Skip to content
← Back

src/syntax/parsers/statements/node.ghul

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