Skip to content
← Back

src/syntax/parsers/statements/node.ghul

1
namespace Syntax.Parsers.Statements is
2
use IO.Std
3
use Ghul.Disposable
4
5
use Ghul.Pipes
6
7
class STATEMENT(
8
labellable_statement_tokens: Collections.LIST[Lexical.TOKEN],
9
identifier_parser: Parser[Trees.Identifiers.Identifier],
10
expression_parser: Parser[Trees.Expressions.Expression],
11
expression_list_parser: Parser[Trees.Expressions.LIST],
12
variable_parser: Parser[Trees.Variables.VARIABLE],
13
variable_list_parser: Parser[Trees.Variables.LIST],
14
statement_list_parser: Parser[Trees.Statements.LIST],
15
pragma_parser: Parser[Trees.Statements.PRAGMA]
16
): Base[Trees.Statements.Statement] is
17
description: string => "statement"
18
19
super()
20
21
init(..) is
22
add_parsers()
23
si
24
25
add_parsers() is
26
// note: this has to be the exact same set as accepted by the primary expression parsers
27
// plus TOKEN.OPERAOR. Otherwise return statements with an expression, but without a semicolon,
28
// won't recognize the expression as a primary expression.
29
let primary_expression_tokens = [
30
Lexical.TOKEN.IDENTIFIER,
31
Lexical.TOKEN.SQUARE_OPEN,
32
Lexical.TOKEN.ARRAY_DEF,
33
Lexical.TOKEN.PAREN_OPEN,
34
Lexical.TOKEN.NEW,
35
Lexical.TOKEN.CAST,
36
Lexical.TOKEN.ISA,
37
Lexical.TOKEN.TYPEOF,
38
Lexical.TOKEN.INT_LITERAL,
39
Lexical.TOKEN.FLOAT_LITERAL,
40
Lexical.TOKEN.STRING_LITERAL,
41
Lexical.TOKEN.ENTER_STRING,
42
Lexical.TOKEN.CHAR_LITERAL,
43
Lexical.TOKEN.TRUE,
44
Lexical.TOKEN.FALSE,
45
Lexical.TOKEN.NULL,
46
Lexical.TOKEN.SELF,
47
Lexical.TOKEN.SUPER,
48
Lexical.TOKEN.REC,
49
Lexical.TOKEN.IF,
50
Lexical.TOKEN.CASE,
51
Lexical.TOKEN.LET,
52
Lexical.TOKEN.AWAIT,
53
Lexical.TOKEN.VAL,
54
Lexical.TOKEN.DO,
55
Lexical.TOKEN.FOR,
56
Lexical.TOKEN.WHILE,
57
Lexical.TOKEN.OPERATOR
58
]
59
60
add_parser(
61
(context) -> Trees.Statements.Statement is
62
let start = context.location
63
if context.next_token(Lexical.TOKEN.LET, syntax_error_message) then
64
let want_dispose mut = false
65
if context.current_token == Lexical.TOKEN.USE then
66
context.next_token()
67
68
want_dispose = true
69
fi
70
71
let variable_list = variable_list_parser.parse(context)!
72
73
if context.current_token == Lexical.TOKEN.IN then
74
context.next_token()
75
76
let expression = expression_parser.parse(context)!
77
let location = start::expression.location
78
79
return Trees.Statements.EXPRESSION(
80
location,
81
Trees.Expressions.LET_IN(
82
location,
83
want_dispose,
84
variable_list,
85
expression
86
)
87
)
88
fi
89
90
return Trees.Statements.LET(start::variable_list.location, want_dispose, variable_list)
91
fi
92
si,
93
Lexical.TOKEN.LET
94
)
95
96
add_parser(
97
(context) is
98
let start = context.location
99
let end mut = context.location
100
101
if context.next_token(Lexical.TOKEN.RETURN, syntax_error_message) then
102
let expression: Trees.Expressions.Expression? mut = null
103
if
104
context.current.token != Lexical.TOKEN.SEMICOLON /\
105
primary_expression_tokens |> any(t => t == context.current.token)
106
then
107
expression = expression_parser.parse(context)!
108
end = expression.location
109
fi
110
111
return Trees.Statements.RETURN(start::end, expression)
112
fi
113
si,
114
Lexical.TOKEN.RETURN
115
)
116
117
add_parser(
118
(context) is
119
let start = context.location
120
let end mut = context.location
121
if context.next_token(Lexical.TOKEN.YIELD, syntax_error_message) then
122
// `in` cannot begin an expression, so its presence
123
// here is unambiguously the yield-every-element form.
124
let all = context.current.token == Lexical.TOKEN.IN
125
126
if all then
127
context.next_token()
128
fi
129
130
let expression = expression_parser.parse(context)!
131
end = expression.location
132
133
if all then
134
return Trees.Statements.YIELD_ALL(start::end, expression)
135
fi
136
137
return Trees.Statements.YIELD(start::end, expression)
138
fi
139
si,
140
Lexical.TOKEN.YIELD
141
)
142
143
add_parser(
144
(context) is
145
let start = context.location
146
let end mut = context.location
147
if context.next_token(Lexical.TOKEN.THROW, syntax_error_message) then
148
let expression: Trees.Expressions.Expression? mut = null
149
if context.current.token != Lexical.TOKEN.SEMICOLON then
150
expression = expression_parser.parse(context)!
151
end = expression.location
152
fi
153
154
return Trees.Statements.THROW(start::end, expression)
155
fi
156
si,
157
Lexical.TOKEN.THROW
158
)
159
160
add_parser(
161
(context) is
162
let start = context.location
163
if context.next_token(Lexical.TOKEN.ASSERT, syntax_error_message) then
164
let end mut = start
165
166
let expression = expression_parser.parse(context)!
167
end = expression.location
168
169
let message: Trees.Expressions.Expression? mut = null
170
171
if context.current_token == Lexical.TOKEN.ELSE /\ context.continues(start) then
172
context.next_token()
173
174
message = expression_parser.parse(context)!
175
end = message.location
176
fi
177
178
if context.current_token == Lexical.TOKEN.IN then
179
context.next_token()
180
181
let inner = expression_parser.parse(context)!
182
let location = start::inner.location
183
184
return Trees.Statements.EXPRESSION(
185
location,
186
Trees.Expressions.ASSERT_IN(
187
location,
188
expression,
189
message,
190
inner
191
)
192
)
193
fi
194
195
let result = Trees.Statements.ASSERT(
196
start::end,
197
expression,
198
message
199
)
200
201
return result
202
fi
203
si,
204
Lexical.TOKEN.ASSERT
205
)
206
207
add_parser(
208
(context) => parse_if(context),
209
Lexical.TOKEN.IF
210
)
211
212
add_parser(
213
(context) is
214
let start = context.location
215
216
context.next_token(Lexical.TOKEN.CASE)
217
218
// An arm's statements end at the next arm as well as at
219
// the `esac`.
220
let use open =
221
context.open_construct(
222
Collections.LIST[Lexical.TOKEN]([
223
Lexical.TOKEN.WHEN,
224
Lexical.TOKEN.ELSE,
225
Lexical.TOKEN.ESAC
226
])
227
)
228
let expression = expression_parser.parse(context)!
229
let seen_default mut = false
230
let saw_malformed_pattern mut = false
231
let match_list = Collections.LIST[Trees.Statements.CASE_MATCH]()
232
233
while
234
context.current.token == Lexical.TOKEN.WHEN \/
235
context.current.token == Lexical.TOKEN.ELSE
236
do
237
let match_start = context.location
238
let match_expressions: Trees.Expressions.LIST? mut = null
239
let match_pattern: Trees.Variables.VARIABLE? mut = null
240
let match_guard: Trees.Expressions.Expression? mut = null
241
let is_malformed_pattern mut = false
242
if context.current.token == Lexical.TOKEN.WHEN then
243
context.next_token(Lexical.TOKEN.WHEN)
244
245
let classification = classify_when_arm(context)
246
247
if classification.is_pattern then
248
let parsed = _parse_pattern(context)
249
250
if parsed? then
251
parsed.mark_refutable()
252
match_pattern = parsed
253
fi
254
255
if is_guard_operator(context) then
256
context.next_token()
257
match_guard = expression_parser.parse(context)
258
fi
259
elif classification.error_message? then
260
context.error(classification.error_location!, classification.error_message!)
261
262
is_malformed_pattern = true
263
saw_malformed_pattern = true
264
265
// classify_when_arm only speculated, so parse
266
// the pattern for real before recovering the
267
// offending shape.
268
_parse_pattern(context)
269
270
if is_guard_operator(context) then
271
context.next_token()
272
expression_parser.parse(context)
273
fi
274
275
if context.current_token == Lexical.TOKEN.PAREN_OPEN then
276
expression_parser.parse(context)
277
else
278
while context.current_token == Lexical.TOKEN.COMMA do
279
context.next_token()
280
_parse_pattern(context)
281
282
if is_guard_operator(context) then
283
context.next_token()
284
expression_parser.parse(context)
285
fi
286
od
287
fi
288
else
289
match_expressions = expression_list_parser.parse(context)
290
fi
291
292
context.next_token(Lexical.TOKEN.THEN)
293
else
294
if seen_default then
295
context.error(context.location, "more than one else arm in case statement")
296
else
297
seen_default = true
298
fi
299
context.next_token(Lexical.TOKEN.ELSE)
300
fi
301
let match_statements = statement_list_parser.parse(context)!
302
let match = Trees.Statements.CASE_MATCH(match_start::match_statements.location, match_expressions, match_pattern, match_guard, match_statements)
303
304
if is_malformed_pattern then
305
match.poison()
306
fi
307
308
match_list.add(match)
309
od
310
311
let result = Trees.Statements.CASE(start::context.location, expression, match_list)
312
context.expect_closer(Lexical.TOKEN.ESAC)
313
314
if saw_malformed_pattern then
315
result.poison()
316
fi
317
318
return result
319
si,
320
Lexical.TOKEN.CASE
321
)
322
323
add_parser(
324
(context) is
325
let start = context.location
326
context.next_token(Lexical.TOKEN.TRY)
327
328
let use open =
329
context.open_construct(
330
Collections.LIST[Lexical.TOKEN]([
331
Lexical.TOKEN.CATCH,
332
Lexical.TOKEN.FINALLY,
333
Lexical.TOKEN.YRT
334
])
335
)
336
337
let body = statement_list_parser.parse(context)!
338
let catches = Collections.LIST[Trees.Statements.CATCH]()
339
340
while context.current.token == Lexical.TOKEN.CATCH do
341
let catch_start = context.location
342
context.next_token()
343
let catch_variable = variable_parser.parse(context)
344
let catch_body = statement_list_parser.parse(context)!
345
catches.add(Trees.Statements.CATCH(catch_start::catch_body.location, catch_variable, catch_body))
346
od
347
348
let `finally: Trees.Statements.LIST? mut = _
349
350
if context.current.token == Lexical.TOKEN.FINALLY then
351
context.next_token()
352
`finally = statement_list_parser.parse(context)
353
fi
354
355
while context.current.token == Lexical.TOKEN.CATCH do
356
context.error(context.location, "catches cannot appear after finally")
357
let catch_start = context.location
358
context.next_token()
359
let catch_variable = variable_parser.parse(context)
360
let catch_body = statement_list_parser.parse(context)!
361
catches.add(Trees.Statements.CATCH(catch_start::catch_body.location, catch_variable, catch_body))
362
od
363
364
let result = Trees.Statements.TRY(start::context.location, start, body, catches, `finally)
365
context.expect_closer(Lexical.TOKEN.YRT)
366
return result
367
si,
368
Lexical.TOKEN.TRY
369
)
370
371
add_parser(
372
(context) is
373
let start = context.location
374
let condition: Trees.Expressions.Expression? mut = _
375
let binding: Trees.Statements.REFUTABLE_BINDING? mut = _
376
377
if context.current.token == Lexical.TOKEN.WHILE then
378
context.next_token()
379
380
if context.current_token == Lexical.TOKEN.LET then
381
binding = try_parse_let_binding(context, Lexical.TOKEN.DO, "while")
382
fi
383
384
if !binding? then
385
condition = expression_parser.parse(context)
386
fi
387
fi
388
389
if context.next_token(Lexical.TOKEN.DO, syntax_error_message) then
390
let use open = context.open_construct(Lexical.TOKEN.OD)
391
392
let body = statement_list_parser.parse(context)!
393
394
let result = Trees.Statements.DO(start::context.location, condition, binding, body)
395
396
context.expect_closer(Lexical.TOKEN.OD)
397
398
return result
399
else
400
return
401
Trees.Statements.DO(
402
start::context.current.location,
403
condition,
404
binding,
405
Trees.Statements.LIST(start::context.current.location, Collections.LIST[Trees.Statements.Statement](0)))
406
fi
407
si,
408
[Lexical.TOKEN.WHILE, Lexical.TOKEN.DO]
409
)
410
411
add_parser(
412
(context) is
413
let start = context.location
414
context.next_token(Lexical.TOKEN.FOR)
415
416
let variable = variable_parser.parse(context)
417
let expression: Trees.Expressions.Expression? mut = _
418
let body: Trees.Statements.LIST? mut = _
419
420
if (variable? /\ !variable.is_poisoned) \/ context.current_token == Lexical.TOKEN.IN then
421
if context.next_token(Lexical.TOKEN.IN, "in for statement") then
422
expression = expression_parser.parse(context)
423
fi
424
fi
425
426
if (expression? /\ !expression.is_poisoned) \/ context.current_token == Lexical.TOKEN.DO then
427
if context.next_token(Lexical.TOKEN.DO, "in for statement") then
428
let use open = context.open_construct(Lexical.TOKEN.OD)
429
430
body = statement_list_parser.parse(context)
431
fi
432
fi
433
434
let result = Trees.Statements.FOR(start::context.location, variable, expression, body)
435
436
if body? \/ context.current_token == Lexical.TOKEN.OD then
437
context.expect_closer(Lexical.TOKEN.OD, "in for statement")
438
fi
439
440
if variable? \/ expression? \/ body? then
441
return result
442
fi
443
si,
444
Lexical.TOKEN.FOR
445
)
446
447
add_parser(
448
(context) is
449
let start = context.location
450
let end mut = context.location
451
context.next_token()
452
let expression: Trees.Expressions.Expression? mut = _
453
454
if
455
context.current.token != Lexical.TOKEN.SEMICOLON /\
456
primary_expression_tokens |> any(t => t == context.current.token)
457
then
458
expression = expression_parser.parse(context)!
459
end = expression.location
460
fi
461
462
return Trees.Statements.BREAK(start::end, expression)
463
si,
464
Lexical.TOKEN.BREAK
465
)
466
467
add_parser(
468
(context) is
469
let start = context.location
470
let end mut = context.location
471
context.next_token()
472
let label: Trees.Identifiers.Identifier? mut = _
473
if context.current.token == Lexical.TOKEN.IDENTIFIER then
474
let parsed_label = identifier_parser.parse(context)!
475
label = parsed_label
476
end = parsed_label.location
477
fi
478
479
let result = Trees.Statements.CONTINUE(start::end, label)
480
481
return result
482
si,
483
Lexical.TOKEN.CONTINUE
484
)
485
486
add_parser(
487
(context) => pragma_parser.parse(context)!,
488
Lexical.TOKEN.AT
489
)
490
si
491
492
parse_if(context: CONTEXT) -> Trees.Statements.IF is
493
let start = context.location
494
495
// A branch's statements end at the next branch as well as at
496
// the `fi`.
497
let use open =
498
context.open_construct(
499
Collections.LIST[Lexical.TOKEN]([
500
Lexical.TOKEN.ELIF,
501
Lexical.TOKEN.ELSE,
502
Lexical.TOKEN.FI
503
])
504
)
505
506
let branches = Collections.LIST[Trees.Statements.IF_BRANCH]()
507
508
let should_poison mut = false
509
510
// Every other block statement spans to its own closing keyword -
511
// DO, FOR and CASE are all built before consuming it. The `fi`
512
// is consumed inside the loop below, so its position has to be
513
// carried out explicitly or the span runs to the token after it.
514
let fi_location: Source.LOCATION? mut = _
515
516
do
517
let branch_start = context.location
518
let condition: Trees.Expressions.Expression? mut = _
519
let binding: Trees.Statements.REFUTABLE_BINDING? mut = _
520
521
if
522
context.current.token == Lexical.TOKEN.ELIF \/ context.current.token == Lexical.TOKEN.IF
523
then
524
context.next_token()
525
526
if context.current_token == Lexical.TOKEN.LET then
527
binding = try_parse_let_binding(context, Lexical.TOKEN.THEN, "if")
528
fi
529
530
if binding? then
531
if binding.is_poisoned then
532
should_poison = true
533
if context.current_token != Lexical.TOKEN.THEN then
534
break
535
fi
536
fi
537
else
538
condition = expression_parser.parse(context)!
539
540
if condition.is_poisoned then
541
should_poison = true
542
if context.current_token != Lexical.TOKEN.THEN then
543
break
544
fi
545
fi
546
fi
547
548
if !context.next_token(Lexical.TOKEN.THEN) then
549
should_poison = true
550
branches.add(
551
Trees.Statements.IF_BRANCH(
552
branch_start::context.current.location,
553
condition,
554
binding,
555
Trees.Statements.LIST(
556
start::context.current.location,
557
Collections.LIST[Trees.Statements.Statement](0)
558
)
559
)
560
)
561
562
if
563
context.current_token != Lexical.TOKEN.FI /\
564
context.current_token != Lexical.TOKEN.ELIF /\
565
context.current_token != Lexical.TOKEN.ELSE then
566
break
567
fi
568
fi
569
else
570
condition = null
571
if !context.next_token(Lexical.TOKEN.ELSE) then
572
should_poison = true
573
break
574
fi
575
fi
576
577
let body = statement_list_parser.parse(context)
578
579
if body? then
580
if body.is_poisoned then
581
should_poison = true
582
fi
583
584
branches.add(Trees.Statements.IF_BRANCH(branch_start::body.location, condition, binding, body))
585
fi
586
587
if context.current.token == Lexical.TOKEN.FI then
588
fi_location = context.current.location
589
context.next_token()
590
break
591
fi
592
od
593
594
let span_end = if let end: Source.LOCATION = fi_location then end else context.location fi
595
596
let result = Trees.Statements.IF(start::span_end, branches)
597
598
result.poison(should_poison)
599
600
return result
601
si
602
603
// Parse an `if let` / `while let` clause list, disambiguating a
604
// conditional definition (`if let x = e then`, `if let x = e, y =
605
// f then`) from a let-in expression used as the condition (`if let
606
// x = e in body then`). The clauses are accepted only when the
607
// expected terminator (`THEN` for `if let`, `DO` for `while let`)
608
// follows: on a match the speculation is committed and the binding
609
// returned; otherwise it is rolled back and null returned, leaving
610
// the caller to parse the condition as an expression. Parsing once
611
// and committing — rather than probing and then re-parsing — keeps
612
// each clause off the tokenizer's re-scan path, so a speculation
613
// inside a clause is not counted twice by the loop detector. The
614
// `let` is current on entry and is consumed as part of the
615
// speculation.
616
try_parse_let_binding(
617
context: CONTEXT,
618
terminator: Lexical.TOKEN,
619
kind: string
620
) -> Trees.Statements.REFUTABLE_BINDING? is
621
let use diagnostics_snapshot = context.diagnostics_speculate_then_backtrack()
622
let use snapshot = context.tokenizer_speculate_then_backtrack()
623
624
context.next_token()
625
626
let binding = parse_let_binding(context, kind)
627
628
if binding? /\ context.current_token == terminator then
629
snapshot.commit()
630
diagnostics_snapshot.commit()
631
632
return binding
633
fi
634
635
return null
636
si
637
638
// A `/\` after a `when` pattern's own shape (before `then`) is a
639
// guard, not a continuation of the pattern — `variable_parser`
640
// never consumes it (unlike a generic type-bound `[T: A /\ B]`,
641
// `Shape.CIRCLE` is a qualified name so the intersection-bound
642
// branch in the type-expression parser never fires here).
643
is_guard_operator(context: CONTEXT) -> bool =>
644
context.current.token == Lexical.TOKEN.OPERATOR /\
645
context.current.value_string =~ "/\\"
646
647
// Parse a pattern in a position where it can fail to match, so
648
// a leaf may carry the `~` match marker.
649
_parse_pattern(context: CONTEXT) -> Trees.Variables.VARIABLE? is
650
let previous_in_refutable_pattern = context.in_refutable_pattern
651
context.in_refutable_pattern = true
652
653
try
654
return variable_parser.parse(context)
655
finally
656
context.in_refutable_pattern = previous_in_refutable_pattern
657
yrt
658
si
659
660
// Disambiguate a `when` arm's pattern from a value-equality
661
// expression list. Speculatively parse a variable (and, if
662
// present, its guard); treat it as a pattern only if it carries
663
// actual pattern shape — an ascription (`v: T`, `_: T`, or
664
// per-element-ascribed destructure) or a destructure (`(a, b)`).
665
// A bare identifier without ascription is an expression
666
// (equality test against the resolved constant), not a binding
667
// — bindings always carry shape information.
668
//
669
// Also flags two pattern-shaped forms that parse just far enough
670
// to look like a pattern before diverging from `then`: a
671
// constructor-argument group after the ascribed type (`when _:
672
// T(args) then`) and a comma-separated list of binding patterns
673
// in one arm. Neither is grammatical, and both would otherwise
674
// reach a later pass half-built, so each is reported here as the
675
// syntax error it is and its arm skipped.
676
classify_when_arm(context: CONTEXT) -> (is_pattern: bool, error_message: string?, error_location: Source.LOCATION?) is
677
let use diagnostics_snapshot = context.diagnostics_speculate_then_backtrack()
678
let use snapshot = context.tokenizer_speculate_then_backtrack()
679
680
let probe = _parse_pattern(context)
681
682
if !probe? then
683
return (false, null, null)
684
fi
685
686
if is_guard_operator(context) then
687
context.next_token()
688
expression_parser.parse(context)
689
fi
690
691
let has_pattern_shape = probe.is_explicit_type \/ !probe.left.is_simple_name
692
693
if context.current_token == Lexical.TOKEN.THEN then
694
return (has_pattern_shape, null, null)
695
fi
696
697
if has_pattern_shape then
698
// Captured before the speculation unwinds, so the caller
699
// can report at the offending token rather than at the
700
// arm's leading pattern-variable token.
701
let offending_location = context.location
702
703
if
704
context.current_token == Lexical.TOKEN.PAREN_OPEN \/
705
context.current_token == Lexical.TOKEN.COMMA
706
then
707
return (
708
false,
709
"syntax error: expected {Lexical.TOKEN_NAMES[Lexical.TOKEN.THEN]} but found {context.current_token_name}",
710
offending_location
711
)
712
fi
713
fi
714
715
return (false, null, null)
716
si
717
718
parse_let_binding(context: CONTEXT, kind: string) -> Trees.Statements.REFUTABLE_BINDING? is
719
let start = context.location
720
let clauses = Collections.LIST[Trees.Statements.REFUTABLE_BINDING_CLAUSE]()
721
722
let previous_in_refutable_pattern = context.in_refutable_pattern
723
context.in_refutable_pattern = true
724
725
try
726
return _parse_clauses(context, kind, start, clauses)
727
finally
728
context.in_refutable_pattern = previous_in_refutable_pattern
729
yrt
730
si
731
732
_parse_clauses(
733
context: CONTEXT,
734
kind: string,
735
start: Source.LOCATION,
736
clauses: Collections.MutableList[Trees.Statements.REFUTABLE_BINDING_CLAUSE]
737
) -> Trees.Statements.REFUTABLE_BINDING? is
738
let should_poison mut = false
739
740
do
741
let clause = _parse_clause(context, kind)
742
743
if !clause? then
744
return null
745
fi
746
747
if clause.is_poisoned then
748
should_poison = true
749
fi
750
751
clauses.add(clause)
752
753
if context.current_token != Lexical.TOKEN.COMMA then
754
break
755
fi
756
757
context.next_token()
758
od
759
760
if clauses.count == 0 then
761
return null
762
fi
763
764
let result =
765
Trees.Statements.REFUTABLE_BINDING(
766
start::context.location,
767
clauses
768
)
769
770
result.poison(should_poison)
771
772
return result
773
si
774
775
// Parse one clause of an `if let` / `while let` clause list.
776
// Two forms share the clause position:
777
//
778
// pattern [: T] = e [/\ guard] — the full form
779
// path [/\ guard] — leaf-name shorthand
780
// path? [/\ guard] — leaf-name shorthand, explicit test
781
// path: T [/\ guard] — leaf-name shorthand, narrowed
782
//
783
// A destructure or wildcard pattern can't open a path, so a
784
// clause not starting with an identifier or self parses as the
785
// full form directly. For the ambiguous openings, parse the
786
// left-hand side as an expression first — it covers both a
787
// simple pattern name and any path shape (`a.b`, `a[i].b`,
788
// `f().b`) — then discriminate on what follows: an `=` (after
789
// an optional `: T`) means it was a full-form pattern, which
790
// must then be a simple name; anything else is the shorthand.
791
_parse_clause(context: CONTEXT, kind: string) -> Trees.Statements.REFUTABLE_BINDING_CLAUSE? is
792
if
793
context.current_token != Lexical.TOKEN.IDENTIFIER /\
794
context.current_token != Lexical.TOKEN.SELF
795
then
796
let variable = variable_parser.parse(context)
797
798
if variable? then
799
return _build_clause(context, variable, kind)
800
fi
801
802
return null
803
fi
804
805
let start = context.location
806
807
let expression = expression_parser.parse(context)!
808
809
let narrow_type_expression: Trees.TypeExpressions.TypeExpression? mut = null
810
811
if context.current_token == Lexical.TOKEN.COLON then
812
context.next_token()
813
814
narrow_type_expression = IoC.CONTAINER.instance.type_parser.parse(context)
815
816
// A constructor-argument group after the ascribed type
817
// (`name: T(args) = e`) is not grammatical. Consuming it
818
// here leaves the rest of the clause to parse normally,
819
// so the enclosing `if let` is still recognised as one
820
// and the error does not cascade into a reading of the
821
// condition as a let-in expression.
822
if context.current_token == Lexical.TOKEN.PAREN_OPEN then
823
context.error(
824
context.location,
825
"syntax error: expected {Lexical.TOKEN_NAMES[Lexical.TOKEN.ASSIGN]} but found {context.current_token_name}"
826
)
827
828
expression_parser.parse(context)
829
fi
830
fi
831
832
if context.current_token == Lexical.TOKEN.ASSIGN then
833
return _build_full_clause(context, start, expression, narrow_type_expression, kind)
834
fi
835
836
return _build_shorthand_clause(context, start, expression, narrow_type_expression, kind)
837
si
838
839
// Full-form clause whose left-hand side arrived as an already
840
// parsed expression: `name [: T] = e [/\ guard]`. The `=` is
841
// current; the pattern must be a simple name.
842
_build_full_clause(
843
context: CONTEXT,
844
start: Source.LOCATION,
845
expression: Trees.Expressions.Expression,
846
narrow_type_expression: Trees.TypeExpressions.TypeExpression?,
847
kind: string
848
) -> Trees.Statements.REFUTABLE_BINDING_CLAUSE? is
849
context.next_token()
850
851
let name =
852
if expression.is_unqualified_identifier then
853
expression.try_copy_as_identifer()
854
else
855
null
856
fi
857
858
let initializer = expression_parser.parse(context)!
859
860
if !name? then
861
context.error(expression.location, "expected a simple variable name before = in {kind} let")
862
return null
863
fi
864
865
let pattern_left = Trees.Variables.SIMPLE_VARIABLE_LEFT(expression.location, name)
866
pattern_left.mark_refutable_recursive()
867
868
let split = _split_chain_initializer(initializer)
869
870
let clause =
871
Trees.Statements.REFUTABLE_BINDING_CLAUSE(
872
start::initializer.location,
873
split.initializer,
874
narrow_type_expression,
875
pattern_left,
876
split.guard
877
)
878
879
return clause
880
si
881
882
// Leaf-name shorthand clause: `path`, `path?` or `path: T` with
883
// no pattern and no `=`. Declares an immutable local named after
884
// the path's last member, holding the value (`path` / `path?`) or
885
// its narrowing (`: T`) — `if let x.y.z then f(z)` is
886
// `if let z = x.y.z` with the name inferred, refutable on the
887
// path's own optionality. A trailing `/\ …` chain becomes the
888
// clause guard, as in the full form.
889
_build_shorthand_clause(
890
context: CONTEXT,
891
start: Source.LOCATION,
892
expression: Trees.Expressions.Expression,
893
narrow_type_expression: Trees.TypeExpressions.TypeExpression?,
894
kind: string
895
) -> Trees.Statements.REFUTABLE_BINDING_CLAUSE? is
896
let guard: Trees.Expressions.Expression? mut = null
897
let scrutinee: Trees.Expressions.Expression mut = expression
898
let end mut = expression.location
899
900
if narrow_type_expression? then
901
// `path: T` — the type parser stopped before any
902
// `/\ guard` chain.
903
end = narrow_type_expression.location
904
905
if context.current_token == Lexical.TOKEN.OPERATOR /\ context.current_string =~ "/\\" then
906
context.next_token()
907
guard = expression_parser.parse(context)!
908
909
end = guard.location
910
fi
911
else
912
// `path? [/\ guard…]` or bare `path [/\ guard…]` — parsed
913
// as one expression; the leftmost `/\` leaf is the
914
// scrutinee, optionally carrying a trailing presence test,
915
// and the rest is the guard. Refutability comes from the
916
// scrutinee's own optionality, exactly as in the full form
917
// `if let z = x.y.z` — the `?` is not required.
918
let split = _split_chain_initializer(expression)
919
920
if isa Trees.Expressions.HAS_VALUE(split.initializer) then
921
let has_value = cast Trees.Expressions.HAS_VALUE?(split.initializer)!
922
923
scrutinee = has_value.left
924
elif _infer_leaf_name(split.initializer)? then
925
scrutinee = split.initializer
926
else
927
return _build_missing_test_clause(context, start, expression, kind)
928
fi
929
930
guard = split.guard
931
fi
932
933
let leaf mut = _infer_leaf_name(scrutinee)
934
let can_infer = leaf?
935
936
if !can_infer then
937
if narrow_type_expression? then
938
// An ascribed name with no `= …`: the shorthand lifts
939
// its variable out of a member path, and a bare name
940
// has no path to lift from, so what is missing is the
941
// initializer rather than the name.
942
return _build_missing_test_clause(context, start, expression, kind)
943
fi
944
945
// Still yield a clause — poisoned, with a placeholder
946
// variable — so the rest of the chain parses and the
947
// whole statement is still recognised as {kind} let.
948
context.error(
949
start::end,
950
"cannot infer a variable name for this expression",
951
start::end,
952
"help: use {kind} let name = ...")
953
leaf = Trees.Identifiers.Identifier(scrutinee.location, "value")
954
fi
955
956
let pattern_left = Trees.Variables.SIMPLE_VARIABLE_LEFT(leaf!.location, leaf)
957
pattern_left.mark_refutable_recursive()
958
959
let clause =
960
Trees.Statements.REFUTABLE_BINDING_CLAUSE(
961
start::end,
962
scrutinee,
963
narrow_type_expression,
964
pattern_left,
965
guard
966
)
967
968
clause.is_inferred_name = true
969
clause.poison(!can_infer)
970
971
return clause
972
si
973
974
// A clause whose scrutinee has no name to lift out: a bare local
975
// (a full-form clause missing its `= …`, which is also how a
976
// same-named shadow is steered away from) or an expression with
977
// no path leaf to name a variable after (a call, an index). A
978
// member path never reaches here — it takes the shorthand. Report
979
// the most likely omission and yield a poisoned clause so the rest
980
// of the chain still parses and diagnoses usefully.
981
_build_missing_test_clause(
982
context: CONTEXT,
983
start: Source.LOCATION,
984
expression: Trees.Expressions.Expression,
985
kind: string
986
) -> Trees.Statements.REFUTABLE_BINDING_CLAUSE? is
987
let leaf mut =
988
if expression.is_unqualified_identifier then
989
expression.try_copy_as_identifer()
990
else
991
_infer_leaf_name(expression)
992
fi
993
994
if !leaf? then
995
// A call or index result: nameable only with the full form.
996
context.error(
997
start::expression.location,
998
"cannot infer a variable name for this expression",
999
start::expression.location,
1000
"help: use {kind} let name = ...")
1001
leaf = Trees.Identifiers.Identifier(expression.location, "value")
1002
else
1003
// A bare local: a leaf but no path, so no `= …`.
1004
context.error(expression.location, "{kind} let requires an initializer")
1005
fi
1006
1007
let pattern_left = Trees.Variables.SIMPLE_VARIABLE_LEFT(leaf.location, leaf)
1008
pattern_left.mark_refutable_recursive()
1009
1010
let clause =
1011
Trees.Statements.REFUTABLE_BINDING_CLAUSE(
1012
start::expression.location,
1013
Trees.Expressions.NULL(expression.location),
1014
null,
1015
pattern_left,
1016
null
1017
)
1018
1019
clause.poison(true)
1020
1021
return clause
1022
si
1023
1024
// The shorthand's variable name: the last member of a path. A
1025
// fresh identifier node — the scrutinee keeps its own. A bare
1026
// name has no path to take a leaf from, and the new variable
1027
// would shadow the scrutinee's own name inside the clause's
1028
// scope; narrowing already covers `if x?` on a local.
1029
_infer_leaf_name(scrutinee: Trees.Expressions.Expression) -> Trees.Identifiers.Identifier? is
1030
if isa Trees.Expressions.MEMBER(scrutinee) then
1031
let member = cast Trees.Expressions.MEMBER(scrutinee)
1032
1033
return Trees.Identifiers.Identifier(member.identifier.location, member.identifier.name)
1034
fi
1035
1036
return null
1037
si
1038
1039
// Build one REFUTABLE_BINDING_CLAUSE from a parsed VARIABLE.
1040
// Reports the missing-initializer error and poisons the clause
1041
// when there is no `= …`; otherwise splits the initializer's
1042
// top-level `/\` chain into (scrutinee, guard) per the rule
1043
// described on `_split_chain_initializer`.
1044
_build_clause(
1045
context: CONTEXT,
1046
variable: Trees.Variables.VARIABLE,
1047
kind: string
1048
) -> Trees.Statements.REFUTABLE_BINDING_CLAUSE is
1049
let pattern_left = variable.left
1050
pattern_left.mark_refutable_recursive()
1051
1052
let narrow_type_expression =
1053
if variable.is_explicit_type then
1054
variable.type_expression
1055
else
1056
null
1057
fi
1058
1059
let initializer = variable.initializer
1060
1061
if !initializer? then
1062
context.error(variable.location, "{kind} let requires an initializer")
1063
1064
let clause =
1065
Trees.Statements.REFUTABLE_BINDING_CLAUSE(
1066
variable.location,
1067
Trees.Expressions.NULL(variable.location),
1068
narrow_type_expression,
1069
pattern_left,
1070
null
1071
)
1072
1073
clause.poison(true)
1074
return clause
1075
fi
1076
1077
// Split a top-level `/\` chain in the initializer: the
1078
// leftmost leaf is the real scrutinee (the value to
1079
// test for presence / narrow); the remaining right-hand
1080
// operands form a guard expression evaluated after the
1081
// clause's bindings come into scope, on the then-arm.
1082
// `/\` returns bool, so a top-level `/\` initializer is
1083
// never a sensible refutable value on its own — every
1084
// such tree is a chain.
1085
let split = _split_chain_initializer(initializer)
1086
1087
let clause =
1088
Trees.Statements.REFUTABLE_BINDING_CLAUSE(
1089
variable.location,
1090
split.initializer,
1091
narrow_type_expression,
1092
pattern_left,
1093
split.guard
1094
)
1095
1096
clause.poison(variable.is_poisoned)
1097
1098
return clause
1099
si
1100
1101
// Walk down the left spine of a `/\` tree. The leftmost leaf
1102
// becomes the binding's initializer; the right operands in
1103
// source order are recombined left-assoc as the guard. Returns
1104
// (initializer, null) when the input is not a `/\` BINARY.
1105
_split_chain_initializer(expr: Trees.Expressions.Expression) -> (initializer: Trees.Expressions.Expression, guard: Trees.Expressions.Expression?) is
1106
if !isa Trees.Expressions.BINARY(expr) then
1107
return (expr, null)
1108
fi
1109
1110
let head = cast Trees.Expressions.BINARY(expr)
1111
1112
if !head.actual_operation? \/ !(head.actual_operation =~ "/\\") then
1113
return (expr, null)
1114
fi
1115
1116
let collected = Collections.LIST[Trees.Expressions.Expression]()
1117
let leftmost: Trees.Expressions.Expression mut = expr
1118
1119
do
1120
if !isa Trees.Expressions.BINARY(leftmost) then
1121
break
1122
fi
1123
1124
let cur = cast Trees.Expressions.BINARY(leftmost)
1125
1126
if !cur.actual_operation? \/ !(cur.actual_operation =~ "/\\") then
1127
break
1128
fi
1129
1130
collected.add(cur.right)
1131
leftmost = cur.left
1132
od
1133
1134
// `collected` is in reverse source order (outermost-right
1135
// first). Rebuild the guard left-assoc to match how the
1136
// user wrote it.
1137
let n = collected.count
1138
let guard: Trees.Expressions.Expression mut = collected[n - 1]
1139
let i mut = n - 2
1140
1141
while i >= 0 do
1142
let right = collected[i]
1143
guard = Trees.Expressions.BINARY(
1144
guard.location::right.location,
1145
Trees.Identifiers.Identifier(guard.location, "/\\"),
1146
"/\\",
1147
guard,
1148
right
1149
)
1150
i = i - 1
1151
od
1152
1153
return (leftmost, guard)
1154
si
1155
1156
other_token(context: CONTEXT) -> Trees.Statements.Statement? is
1157
if context.current.token == Lexical.TOKEN.IDENTIFIER then
1158
if _starts_nested_function(context) then
1159
return parse_nested_function(context)
1160
fi
1161
1162
let want_backtrack = true
1163
1164
let use tokenizer_snapshot = context.tokenizer_speculate_then_backtrack()
1165
1166
let label = identifier_parser.parse(context)!
1167
1168
if context.current.token == Lexical.TOKEN.COLON then
1169
context.next_token()
1170
1171
if labellable_statement_tokens.contains(context.current.token) then
1172
// TODO: maybe we don't actually want to commit yet - could this be a broken
1173
// property definition?
1174
1175
tokenizer_snapshot.commit()
1176
1177
let statement = self.parse(context)!
1178
return Trees.Statements.LABELLED(label.location::statement.location, label, statement)
1179
fi
1180
fi
1181
fi
1182
1183
let left = expression_parser.parse(context)!
1184
1185
if isa Trees.Expressions.Literals.NONE(left) then
1186
return null
1187
elif context.current.token == Lexical.TOKEN.ASSIGN then
1188
context.next_token()
1189
1190
let right = parse_assigned_value(context, expression_parser)
1191
1192
return
1193
Trees.Statements.ASSIGNMENT(
1194
left.location::right.location,
1195
left.rewrite_as_assignment_left(),
1196
right)
1197
else
1198
return Trees.Statements.EXPRESSION(left.location, left)
1199
fi
1200
si
1201
1202
1203
// A named function written among a body's statements. Nothing in the
1204
// expression grammar can follow a call's closing `)` with `is`, `=>`
1205
// or `->`, so a bounded peek settles the shape outright and the parse
1206
// that follows needs no speculation of its own.
1207
//
1208
// The scan gives up at the first token a formal parameter list cannot
1209
// contain, which is what keeps it out of a call's arguments. That
1210
// matters beyond saving work: reading into an interpolated string
1211
// argument lexes it in the wrong mode, and the damage outlives the
1212
// backtrack.
1213
_starts_nested_function(context: CONTEXT) -> bool is
1214
if context.in_top_level_statements then
1215
return false
1216
fi
1217
1218
let use snapshot = context.tokenizer_speculate_then_backtrack_bounded()
1219
1220
context.next_token()
1221
1222
if context.current.token != Lexical.TOKEN.PAREN_OPEN then
1223
return false
1224
fi
1225
1226
let depth mut = 0
1227
1228
while !context.is_end_of_file do
1229
let token = context.current.token
1230
1231
if token == Lexical.TOKEN.PAREN_OPEN then
1232
depth = depth + 1
1233
elif token == Lexical.TOKEN.PAREN_CLOSE then
1234
depth = depth - 1
1235
1236
if depth <= 0 then
1237
context.next_token()
1238
1239
return
1240
context.current.token == Lexical.TOKEN.IS \/
1241
context.current.token == Lexical.TOKEN.ARROW_FAT \/
1242
context.current.token == Lexical.TOKEN.ARROW_THIN
1243
fi
1244
elif !_can_appear_in_formal_arguments(token) then
1245
return false
1246
fi
1247
1248
context.next_token()
1249
od
1250
1251
return false
1252
si
1253
1254
// What a formal parameter list is made of: names, their type
1255
// ascriptions and the types themselves, destructure groups, and the
1256
// attribute pragmas a parameter can carry. Everything else - a
1257
// literal, an operator, a keyword that opens an expression - says the
1258
// parentheses hold call arguments instead.
1259
_can_appear_in_formal_arguments(token: Lexical.TOKEN) -> bool =>
1260
token == Lexical.TOKEN.IDENTIFIER \/
1261
token == Lexical.TOKEN.COLON \/
1262
token == Lexical.TOKEN.COMMA \/
1263
token == Lexical.TOKEN.DOT \/
1264
token == Lexical.TOKEN.QUESTION \/
1265
token == Lexical.TOKEN.ARROW_THIN \/
1266
token == Lexical.TOKEN.ARRAY_DEF \/
1267
token == Lexical.TOKEN.SQUARE_OPEN \/
1268
token == Lexical.TOKEN.SQUARE_CLOSE \/
1269
token == Lexical.TOKEN.REF \/
1270
token == Lexical.TOKEN.PTR \/
1271
token == Lexical.TOKEN.AT
1272
1273
// The expression parser builds the literal, taking its name and its
1274
// formals from what would otherwise have been a call; the name is a
1275
// local of the enclosing body, initialized with the literal, so the
1276
// function is reached from the statement after this one onward.
1277
parse_nested_function(context: CONTEXT) -> Trees.Statements.Statement? is
1278
let start = context.location
1279
1280
context.allow_nested_function = true
1281
1282
let parsed = expression_parser.parse(context)
1283
1284
context.allow_nested_function = false
1285
1286
let literal = cast Trees.Expressions.FUNCTION?(parsed)
1287
1288
if !literal? \/ !literal.nested_name? then
1289
if parsed? then
1290
return Trees.Statements.EXPRESSION(parsed.location, parsed)
1291
fi
1292
1293
return null
1294
fi
1295
1296
let name = literal.nested_name!
1297
1298
return Trees.Statements.FUNCTION(start::literal.location, name, literal)
1299
si
1300
si
1301
si