Skip to content
← Back

src/syntax/parsers/expressions/secondary.ghul

1
namespace Syntax.Parsers.Expressions is
2
use IO.Std;
3
4
use Source;
5
use Logging;
6
7
class SECONDARY(
8
identifier_parser: Parser[Trees.Identifiers.Identifier],
9
type_parser: Parser[Trees.TypeExpressions.TypeExpression],
10
type_list_parser: Parser[Trees.TypeExpressions.LIST],
11
expression_parser: Parser[Trees.Expressions.Expression],
12
expression_primary_parser: Parser[Trees.Expressions.Expression],
13
expression_list_parser: Parser[Trees.Expressions.LIST],
14
body_parser: Parser[Trees.Bodies.Body]
15
): Base[Trees.Expressions.Expression] is
16
description: string => "secondary expression";
17
18
super();
19
20
// A token sits in identifier position after a `.` if it
21
// looks like a word (its `value_string` opens with a letter
22
// or underscore) and it shares a line with the dot. The
23
// line guard matters: a `.` at the end of a line followed
24
// by a structural keyword on the next (`namespace Foo.\n is`)
25
// must not eat that keyword.
26
looks_like_partial_member_keyword(context: CONTEXT, dot_location: LOCATION) -> bool is
27
if context.current.token == Lexical.TOKEN.IDENTIFIER then
28
return false;
29
fi
30
31
let s = context.current.value_string;
32
33
// value_string is null for a value-less token
34
@suppress("presence-test-non-optional")
35
if !s? \/ s.length == 0 then
36
return false;
37
fi
38
39
let first = s.get_chars(0);
40
41
if !((first >= 'a' /\ first <= 'z') \/ (first >= 'A' /\ first <= 'Z') \/ first == '_') then
42
return false;
43
fi
44
45
return context.current.location.start_line == dot_location.start_line;
46
si
47
48
// `|` is overloaded postfix pipe-wrap (`Ghul.Pipes.pipe(...)`)
49
// vs infix bitwise OR. It defaults to the infix reading; the
50
// postfix reading wins only when the next token cannot start
51
// an infix right-hand side. The "cannot start" set is
52
// bounded: expression terminators, postfix-only tokens, and
53
// block-structure keywords. Anything else (a primary start,
54
// an `OPERATOR` token that might be a prefix unary, `await`,
55
// `if`, `case`, …) leaves the operator as infix and the
56
// precedence climber takes it from there. Wrap the LHS in
57
// parentheses to force the postfix reading when the operand
58
// would otherwise look infix-shaped: `(xs|)` as a bare
59
// pipe-wrap.
60
token_blocks_infix_rhs(token: Lexical.TOKEN) -> bool static is
61
case token
62
when
63
Lexical.TOKEN.SEMICOLON,
64
Lexical.TOKEN.COMMA,
65
Lexical.TOKEN.PAREN_CLOSE,
66
Lexical.TOKEN.SQUARE_CLOSE,
67
Lexical.TOKEN.END_OF_INPUT,
68
69
Lexical.TOKEN.DOT,
70
Lexical.TOKEN.ASSIGN,
71
Lexical.TOKEN.QUESTION,
72
Lexical.TOKEN.REF,
73
74
Lexical.TOKEN.IS,
75
Lexical.TOKEN.SI,
76
Lexical.TOKEN.FI,
77
Lexical.TOKEN.OD,
78
Lexical.TOKEN.YRT,
79
Lexical.TOKEN.ESAC,
80
Lexical.TOKEN.ELSE,
81
Lexical.TOKEN.ELIF,
82
Lexical.TOKEN.THEN,
83
Lexical.TOKEN.DO,
84
Lexical.TOKEN.UNTIL,
85
Lexical.TOKEN.WHEN,
86
Lexical.TOKEN.IN,
87
Lexical.TOKEN.ARROW_THIN,
88
Lexical.TOKEN.ARROW_FAT,
89
Lexical.TOKEN.CATCH,
90
Lexical.TOKEN.FINALLY,
91
92
Lexical.TOKEN.CONTINUE_STRING,
93
Lexical.TOKEN.EXIT_STRING,
94
Lexical.TOKEN.CANCEL_STRING,
95
Lexical.TOKEN.FORMAT_STRING
96
then
97
return true;
98
else
99
return false;
100
esac
101
si
102
103
// Peek the token after the current one and report whether it
104
// sits in the `token_blocks_infix_rhs` set. The speculate /
105
// backtrack pair rolls the tokenizer back before returning.
106
next_token_blocks_infix_rhs(context: CONTEXT) -> bool is
107
let use snapshot = context.tokenizer_speculate_then_backtrack();
108
109
context.next_token();
110
111
return token_blocks_infix_rhs(context.current.token);
112
si
113
114
parse(context: CONTEXT) -> Trees.Expressions.Expression is
115
let use tokenizer_state = context.tokenizer_speculate_then_commit();
116
117
let start = context.location;
118
let result = expression_primary_parser.parse(context)!;
119
120
return _parse_trailers(context, start, result, tokenizer_state, true);
121
si
122
123
// Consume the postfix trailers (`.member`, `(args)`, `[index]`,
124
// `|>`, …) that follow a primary. `allow_bar_arrow` is false while
125
// parsing the right side of a `|>`, so a following `|>` ends the
126
// current stage rather than nesting - which keeps
127
// `a |> f() |> g()` left-associative. A `|>` inside an argument
128
// list is unaffected: the argument parser re-enters through the
129
// public `parse`, where `allow_bar_arrow` is true again.
130
_parse_trailers(
131
context: CONTEXT,
132
start: LOCATION,
133
result_in: Trees.Expressions.Expression,
134
tokenizer_state: Syntax.Parsers.TOKEN_LOOKAHEAD_SPECULATE_THEN_COMMIT,
135
allow_bar_arrow: bool
136
) -> Trees.Expressions.Expression is
137
let result: Trees.Expressions.Expression mut = result_in;
138
let previous_left: Trees.Expressions.Expression? mut = _;
139
let previous_identifier: Trees.Identifiers.Identifier? mut = _;
140
141
do
142
case context.current.token
143
when Lexical.TOKEN.PAREN_OPEN then
144
context.next_token();
145
let arguments: Trees.Expressions.LIST mut;
146
if context.current.token != Lexical.TOKEN.PAREN_CLOSE then
147
arguments = expression_list_parser.parse(context)!;
148
else
149
arguments = Trees.Expressions.LIST(context.location, Collections.LIST[Trees.Expressions.Expression]());
150
fi
151
let call_expression = Trees.Expressions.CALL(start::context.location, result, arguments);
152
call_expression.rewrite_named_arguments(context.logger);
153
result = call_expression;
154
context.next_token(Lexical.TOKEN.PAREN_CLOSE, syntax_error_message);
155
156
when Lexical.TOKEN.SQUARE_OPEN then
157
context.next_token();
158
159
let done mut = false;
160
161
let left: Trees.Expressions.Expression? mut = _;
162
let identifier: Trees.Identifiers.Identifier? mut = _;
163
164
if result.is_member then
165
left = previous_left;
166
identifier = previous_identifier;
167
168
elif result.is_identifier then
169
left = null;
170
identifier = result.try_copy_as_identifer();
171
fi
172
173
if identifier? then
174
let use tokenizer_state = context.tokenizer_speculate_then_commit();
175
let use logger_state = context.logger_speculate_then_commit();
176
177
let type_arguments = type_list_parser.parse(context);
178
179
if type_arguments? /\ !type_arguments.is_poisoned /\ context.next_token(Lexical.TOKEN.SQUARE_CLOSE) then
180
let index_expression: Trees.Expressions.Expression? mut = _;
181
182
if type_arguments.count == 1 then
183
let index = type_arguments.elements[0].try_copy_as_value_expression();
184
if index? then
185
index_expression = Trees.Expressions.INDEX(start::context.location, result, index);
186
fi
187
fi;
188
189
done = true;
190
191
if index_expression? then
192
result = Trees.Expressions.AMBIGUOUS_EXPRESSION(
193
start::context.location,
194
index_expression,
195
left,
196
identifier,
197
type_arguments
198
);
199
else
200
result = Trees.Expressions.GENERIC_APPLICATION(
201
start::context.location,
202
left,
203
identifier,
204
type_arguments
205
);
206
fi
207
208
tokenizer_state.commit();
209
logger_state.commit();
210
211
// FIXME: continue generates incorrect code here
212
// continue;
213
fi
214
215
tokenizer_state.backtrack_if_speculating();
216
logger_state.backtrack_if_speculating();
217
fi
218
219
if !done then
220
let index = expression_parser.parse(context)!;
221
222
result = Trees.Expressions.INDEX(start::context.location, result, index);
223
224
context.next_token(Lexical.TOKEN.SQUARE_CLOSE, syntax_error_message);
225
fi
226
227
when Lexical.TOKEN.DOT then
228
let completion_target_start = context.location;
229
230
context.next_token();
231
232
if
233
context.current.token == Lexical.TOKEN.IDENTIFIER
234
then
235
let member = identifier_parser.parse(context)!;
236
237
previous_left = result;
238
previous_identifier = member;
239
240
result = Trees.Expressions.MEMBER(start::member.location, result, member, completion_target_start::member.location);
241
elif looks_like_partial_member_keyword(context, completion_target_start) then
242
// Mid-typing case like `value.is` (en route to
243
// `value.is_empty`). The token is a reserved
244
// word the tokenizer has already classified,
245
// but it sits in identifier position after a
246
// `.`; treat it as a partial member name so
247
// the completer has a MEMBER node with a
248
// populated `right` to anchor suggestions on.
249
// The same-line guard prevents this consuming
250
// a structural keyword on the next line
251
// (`namespace Test.\n is`).
252
let name = context.current.value_string;
253
let member = Trees.Identifiers.Identifier(context.location, name);
254
context.next_token();
255
256
previous_left = result;
257
previous_identifier = member;
258
259
result = Trees.Expressions.MEMBER(start::member.location, result, member, completion_target_start::member.location);
260
elif
261
context.current.token == Lexical.TOKEN.INT_LITERAL \/
262
context.current.token == Lexical.TOKEN.FLOAT_LITERAL \/
263
context.current.token == Lexical.TOKEN.DOUBLE_LITERAL
264
then
265
// `.` followed by a numeric literal, e.g. `p.0`.
266
// Positional tuple members are named by their
267
// index, but a bare number is not an identifier, so
268
// the index must be backtick-escaped (`p.` followed
269
// by a backtick and the number). Point at that, then
270
// consume the number so it doesn't linger and
271
// cascade into a spurious "expected ;" on the rest of
272
// the statement. Recover with a poisoned placeholder
273
// identifier so later passes that assume
274
// MEMBER.identifier is non-null don't crash.
275
context.error(context.location, "a numeric member name must be escaped with a leading backtick");
276
let member_location = context.location;
277
context.next_token();
278
result = Trees.Expressions.MEMBER(start::member_location, result, Trees.Identifiers.Identifier(completion_target_start, null), completion_target_start::member_location);
279
else
280
// `.` not followed by a member name: expect_token
281
// reports it. Recover with a poisoned placeholder
282
// identifier rather than null — later passes assume
283
// MEMBER.identifier is non-null and would otherwise
284
// crash dereferencing it. The offending token is left
285
// for the enclosing context to resynchronise on.
286
context.expect_token(Lexical.TOKEN.IDENTIFIER);
287
result = Trees.Expressions.MEMBER(start..context.location, result, Trees.Identifiers.Identifier(completion_target_start, null), completion_target_start::context.location);
288
fi
289
290
when Lexical.TOKEN.ARROW_THIN, Lexical.TOKEN.ARROW_FAT, Lexical.TOKEN.IS, Lexical.TOKEN.REC then
291
// we could be in a global function or in a method. If the left part looks like it could be a function or method definition
292
// then it's an error and we need to recover.
293
294
// we'll need a heuristic to try to decide if the user is trying to define a nested function or if they've missed off a closing `si`
295
// and actually this is a method definition (if we're in a classy context) or a global function definition (if we're not in a classy context)
296
297
let is_nested_function_definition mut = false;
298
299
if result.could_be_nested_function_definition /\ tokenizer_state.is_speculating then
300
// TODO need same logic but for nested within a global function
301
if context.in_member then
302
if result.location.start_column > context.member_indent then
303
// probably an attempt at nesting a named function definition
304
is_nested_function_definition = true;
305
elif
306
result.location.start_column <= context.member_indent /\
307
result.location.start_column > context.global_indent
308
then
309
// probably a missing `si` in a preceding member definition
310
context.error(result.location, "expected 'si' after member definition");
311
312
tokenizer_state.backtrack();
313
314
throw UNWIND_TO_MEMBER_EXCEPTION(null);
315
fi
316
elif context.in_global_function then
317
if result.location.start_column > context.global_indent then
318
// probably an attempt at nesting a named function definition
319
is_nested_function_definition = true;
320
else
321
// probably a missing `si` in a preceding member definition
322
context.error(result.location, "expected 'si' after member definition");
323
324
tokenizer_state.backtrack();
325
326
throw UNWIND_TO_GLOBAL_EXCEPTION(null);
327
fi
328
elif context.in_classy then
329
// not clear how we might have ended up here
330
tokenizer_state.backtrack();
331
332
throw UNWIND_TO_GLOBAL_EXCEPTION(null);
333
fi
334
fi
335
336
// it's unlikely to be member or global function definition, so we should
337
// parse it as a function literal, even if it has a name or is otherwise garbled.
338
// In particular, if has a block body delimited with with is / si, then we want to
339
// consume that block, otherwise our view of the block structure will get out of sync
340
// and we'll think we've let the enclosing function or method when we reach the closing
341
// `si` of the function literal
342
343
let should_poison mut = false;
344
345
if result.could_be_nested_function_definition then
346
should_poison = true;
347
context.error(result.location, "nested function definition");
348
elif !result.could_be_formal_argument then
349
should_poison = true;
350
context.error(result.location, "expected function literal formal arguments");
351
fi
352
353
let type_expression: Trees.TypeExpressions.TypeExpression mut;
354
let arguments: Trees.Expressions.LIST mut;
355
356
if context.current.token == Lexical.TOKEN.ARROW_THIN then
357
context.next_token();
358
type_expression = type_parser.parse(context)!;
359
type_expression.check_is_not_reference(context.logger, "function cannot return a reference");
360
else
361
type_expression = Trees.TypeExpressions.INFER(start::result.location);
362
fi
363
364
if context.expect_token([Lexical.TOKEN.ARROW_FAT, Lexical.TOKEN.IS, Lexical.TOKEN.REC]) then
365
if should_poison then
366
// error already reported
367
arguments = Trees.Expressions.LIST(start::result.location, Collections.LIST[Trees.Expressions.Expression]());
368
elif isa Trees.Expressions.TUPLE(result) then
369
arguments = result.elements;
370
arguments.rewrite_as_variables();
371
else
372
let elements = Collections.LIST[Trees.Expressions.Expression]();
373
elements.add(result);
374
arguments = Trees.Expressions.LIST(start::result.location, elements);
375
fi
376
377
let is_recursive =
378
if context.current.token == Lexical.TOKEN.REC then
379
context.next_token();
380
true;
381
else
382
false;
383
fi;
384
385
// A lambda body is a fresh statement context. The
386
// enclosing argument list leaves allow_tuple_element
387
// set while an argument expression parses, so without
388
// this reset the body's leading `x = e` statement is
389
// consumed as a formal-argument-style variable
390
// declaration rather than an assignment.
391
context.allow_tuple_element = false;
392
393
let body = body_parser.parse(context)!;
394
395
result =
396
Trees.Expressions.FUNCTION(
397
start::body.location,
398
arguments,
399
type_expression,
400
body,
401
is_recursive
402
);
403
404
if should_poison then
405
result.poison();
406
fi
407
408
return result;
409
else
410
// TODO: is this possible?
411
return Trees.Expressions.Literals.NONE(start::context.location);
412
fi
413
414
when Lexical.TOKEN.QUESTION then
415
let question_location = context.location;
416
tokenizer_state.commit_if_speculating();
417
418
context.next_token();
419
420
if context.current.token == Lexical.TOKEN.DOT then
421
// `?.` coalescing member access. Tokenizer
422
// breaks `?.` into two adjacent tokens (per
423
// the `?`/`!` followed-by-`.` rule in
424
// read_operator), so we recognise the
425
// sequence here.
426
let completion_target_start = question_location;
427
context.next_token();
428
429
if context.current.token == Lexical.TOKEN.IDENTIFIER then
430
let member = identifier_parser.parse(context)!;
431
432
previous_left = result;
433
previous_identifier = member;
434
435
let member_node =
436
Trees.Expressions.MEMBER(
437
start::member.location,
438
result,
439
member,
440
completion_target_start::member.location
441
);
442
443
member_node.is_coalesce = true;
444
result = member_node;
445
else
446
// `?.` not followed by a member name: recover
447
// with a poisoned placeholder identifier, not
448
// null, so passes assuming MEMBER.identifier is
449
// non-null don't crash.
450
context.expect_token(Lexical.TOKEN.IDENTIFIER);
451
452
let member_node =
453
Trees.Expressions.MEMBER(
454
start..context.location,
455
result,
456
Trees.Identifiers.Identifier(completion_target_start, null),
457
completion_target_start::context.location
458
);
459
460
member_node.is_coalesce = true;
461
result = member_node;
462
fi
463
else
464
result = Trees.Expressions.HAS_VALUE(start::question_location, result);
465
fi
466
467
when Lexical.TOKEN.REF then
468
result = Trees.Expressions.REFERENCE(start::context.location, result);
469
tokenizer_state.commit_if_speculating();
470
471
context.next_token();
472
473
when Lexical.TOKEN.OPERATOR then
474
tokenizer_state.commit_if_speculating();
475
476
if context.current_string =~ "|" then
477
if !next_token_blocks_infix_rhs(context) then
478
return result;
479
fi
480
481
let location = context.location;
482
483
let ghul = Trees.Identifiers.Identifier(location, "Ghul");
484
let pipes = Trees.Identifiers.QUALIFIED(location, ghul, "Pipes", location, location);
485
let pipe = Trees.Identifiers.QUALIFIED(location, pipes, "pipe", location, location);
486
487
let function = Trees.Expressions.IDENTIFIER(location, pipe);
488
489
let arguments = Trees.Expressions.LIST(
490
result.location,
491
[result]
492
);
493
494
let pipe_call =
495
Trees.Expressions.CALL(
496
start::location,
497
function,
498
arguments
499
);
500
501
pipe_call.is_pipe_wrap = true;
502
503
result = pipe_call;
504
505
context.next_token();
506
elif context.current_string =~ "!" then
507
result = Trees.Expressions.UNWRAP(start::context.location, result);
508
509
context.next_token();
510
else
511
return result;
512
fi
513
514
when Lexical.TOKEN.SQUARE_OPEN_TICK then
515
tokenizer_state.commit_if_speculating();
516
517
context.next_token();
518
519
let type_arguments = type_list_parser.parse(context)!;
520
521
let left: Trees.Expressions.Expression? mut = _;
522
let identifier: Trees.Identifiers.Identifier? mut = _;
523
524
if result.is_member then
525
left = previous_left;
526
identifier = previous_identifier;
527
528
elif result.is_identifier then
529
left = null;
530
identifier = result.try_copy_as_identifer();
531
fi
532
533
if identifier? then
534
result = Trees.Expressions.GENERIC_APPLICATION(
535
start::context.location,
536
left,
537
identifier!,
538
type_arguments
539
);
540
else
541
result.poison();
542
context.error(result.location, "cannot apply type arguments to this");
543
fi
544
545
context.next_token(Lexical.TOKEN.SQUARE_CLOSE);
546
547
when Lexical.TOKEN.BAR_ARROW then
548
if !allow_bar_arrow then
549
return result;
550
fi
551
552
context.next_token();
553
554
let pipe_start = context.location;
555
let rhs =
556
_parse_trailers(context, pipe_start, expression_primary_parser.parse(context)!, tokenizer_state, false);
557
558
let rhs_call = cast Trees.Expressions.CALL?(rhs);
559
560
if rhs_call? then
561
// Thread the subject in as the first actual argument
562
// and flag the call, so every later pass sees an
563
// ordinary call.
564
rhs_call!.arguments.expressions.insert(0, result);
565
rhs_call!.is_thread_first = true;
566
result = rhs_call!;
567
else
568
context.error(rhs.location, "the right side of |> must be a function call");
569
rhs.poison();
570
result = rhs;
571
fi
572
573
else
574
return result;
575
esac
576
od
577
si
578
si
579
si