Skip to content
← Back

src/syntax/parsers/definitions/function.ghul

1
namespace Syntax.Parsers.Definitions is
2
3
use Source
4
use Logging
5
use Ghul.Disposable
6
7
use Ghul.Pipes
8
9
class FUNCTION(
10
identifier_function_name_parser: Parser[Trees.Identifiers.Identifier],
11
type_parser: Parser[Trees.TypeExpressions.TypeExpression],
12
type_list_parser: Parser[Trees.TypeExpressions.LIST],
13
modifier_list_parser: Parser[Trees.Modifiers.LIST],
14
body_parser: Parser[Trees.Bodies.Body],
15
variable_list_parser: Parser[Trees.Variables.LIST]
16
): Base[Trees.Definitions.FUNCTION] is
17
super()
18
19
parse_generic_arguments(
20
context: CONTEXT
21
) -> Syntax.Trees.TypeExpressions.LIST is
22
if context.current.token == Lexical.TOKEN.SQUARE_OPEN then
23
context.next_token()
24
25
context.in_generic_parameter_list = true
26
let generic_arguments = type_list_parser.parse(context)!
27
context.in_generic_parameter_list = false
28
29
generic_arguments.check_no_reference_types(context.logger)
30
31
let is_poisoned mut = generic_arguments.is_poisoned
32
33
if !is_poisoned \/ context.current.token == Lexical.TOKEN.SQUARE_CLOSE then
34
is_poisoned = !context.next_token(Lexical.TOKEN.SQUARE_CLOSE) \/ is_poisoned
35
fi
36
37
generic_arguments.poison(is_poisoned)
38
39
return generic_arguments
40
fi
41
return Syntax.Trees.TypeExpressions.LIST(context.location, Collections.LIST[Syntax.Trees.TypeExpressions.TypeExpression](0))
42
si
43
44
parse_formal_arguments(
45
context: CONTEXT,
46
start: Source.LOCATION,
47
name: Syntax.Trees.Identifiers.Identifier?,
48
generic_arguments: Syntax.Trees.TypeExpressions.LIST
49
) ->
50
(arguments: Trees.Variables.LIST, function: Trees.Definitions.FUNCTION?)
51
is
52
let is_poisoned mut = false
53
let arguments_open_parenthesis_location = context.location
54
55
context.next_token(Lexical.TOKEN.PAREN_OPEN)
56
57
let use arguments_tokenizer_snapshot = context.tokenizer_speculate_then_commit()
58
let use arguments_logger_snaphot = context.logger_speculate_then_commit()
59
60
let arguments: Trees.Variables.LIST mut = Trees.Variables.LIST(context.location, Collections.LIST[Trees.Variables.VARIABLE](0))
61
let function: Trees.Definitions.FUNCTION? mut = null
62
63
let any_bad_arguments mut = false
64
65
if context.current_token != Lexical.TOKEN.PAREN_CLOSE then
66
// The `..` splice marker tokenizes as OPERATOR, not
67
// IDENTIFIER/AT, so `init(..)` would otherwise trip the
68
// "did we run off the end of the signature" heuristic
69
// below and poison the whole definition even though
70
// nothing is wrong with it.
71
let starts_with_splice =
72
context.current.token == Lexical.TOKEN.OPERATOR /\
73
context.current.value_string =~ ".."
74
75
if
76
context.current.token != Lexical.TOKEN.IDENTIFIER /\
77
context.current.token != Lexical.TOKEN.AT /\
78
!starts_with_splice
79
then
80
is_poisoned = true
81
fi
82
83
let in_init = name? /\ name.name =~ "init"
84
let previous_in_init_arguments = context.in_init_arguments
85
let previous_in_formal_arguments = context.in_formal_arguments
86
87
if in_init then
88
context.in_init_arguments = true
89
fi
90
91
context.in_formal_arguments = true
92
93
try
94
arguments = variable_list_parser.parse(context)!
95
finally
96
context.in_init_arguments = previous_in_init_arguments
97
context.in_formal_arguments = previous_in_formal_arguments
98
yrt
99
100
any_bad_arguments = arguments.is_poisoned
101
102
let last_valid_argument_line mut = 0
103
104
for (index, a) in arguments |> index() do
105
if !a.is_splice then
106
a.mark_argument()
107
a.left.mark_argument_recursive()
108
fi
109
110
if a.is_poisoned then
111
is_poisoned = true
112
any_bad_arguments = true
113
114
elif a.is_splice then
115
// The `..` splice marker carries no type and
116
// no name. The rewrite-primary-constructors
117
// pass expands it into the surrounding class's
118
// primary parameters; skip the
119
// explicit-argument-type check for it. The
120
// variable parser already rejected `..` in any
121
// non-init position via `in_init_arguments`,
122
// so reaching here means we're in an init.
123
last_valid_argument_line = a.location.start_line
124
elif
125
!a.left.is_simple_name /\
126
a.left.has_named_group
127
then
128
context.error(a.location, "named destructuring is not supported in a formal argument list")
129
a.poison(true)
130
is_poisoned = true
131
any_bad_arguments = true
132
elif a.type_expression.is_inferred then
133
context.error(a.location, "explicit argument type required")
134
a.poison(true)
135
is_poisoned = true
136
any_bad_arguments = true
137
else
138
if a.initializer? /\ !isa Trees.Expressions.DEFAULT(a.initializer) then
139
context.error(a.initializer.location, "default value of an argument must be _")
140
fi
141
142
last_valid_argument_line =
143
if a.left.is_simple_name then a.name!.location.start_line else a.location.start_line fi
144
fi
145
od
146
147
if
148
context.current_token == Lexical.TOKEN.PAREN_OPEN \/
149
(context.current_token == Lexical.TOKEN.IDENTIFIER /\ context.location.start_line > arguments.location.end_line)
150
then
151
// we might have run off the end of an incomplete function
152
// signature into the start of a following function
153
// signature
154
155
let could_be_next_function mut = false
156
157
// TODO not sure all these conditions are necessary/meaningful
158
if arguments.variables.count == 0 then
159
could_be_next_function = true
160
elif any_bad_arguments /\ context.location.start_line > arguments.location.end_line then
161
could_be_next_function = true
162
elif last_valid_argument_line < context.location.start_line then
163
could_be_next_function = true
164
fi
165
166
if could_be_next_function then
167
arguments_logger_snaphot.backtrack()
168
169
for a in arguments do
170
a.poison(true)
171
od
172
173
let roll_forwards_to_line = context.location.start_line
174
175
arguments_tokenizer_snapshot.backtrack()
176
177
while context.location.start_line < roll_forwards_to_line do
178
context.next_token()
179
od
180
181
function =
182
Trees.Definitions.FUNCTION(
183
start::(name?.location ?? context.location),
184
name,
185
generic_arguments,
186
arguments,
187
Trees.TypeExpressions.INFER(context.location),
188
Trees.Modifiers.LIST(context.location, null, null),
189
Trees.Bodies.NULL(context.location)
190
)
191
192
context.logger.error(
193
if arguments.variables.count > 0 then
194
arguments.location
195
else
196
arguments_open_parenthesis_location
197
fi,
198
"syntax error: incomplete formal arguments"
199
)
200
201
return (arguments, function)
202
fi
203
fi
204
fi
205
206
arguments_tokenizer_snapshot.commit()
207
arguments_logger_snaphot.commit()
208
209
// TODO: could recognize that no closing parenthesis followed by a line then
210
// an identifier and an opening paren is probably another function signature
211
212
if !is_poisoned \/ context.current.token == Lexical.TOKEN.PAREN_CLOSE then
213
is_poisoned = !context.next_token(Lexical.TOKEN.PAREN_CLOSE) \/ is_poisoned
214
fi
215
216
arguments.poison(is_poisoned)
217
218
return (arguments, function)
219
si
220
221
parse_return_type(
222
context: CONTEXT,
223
start: Source.LOCATION,
224
name: Syntax.Trees.Identifiers.Identifier?,
225
generic_arguments: Syntax.Trees.TypeExpressions.LIST,
226
arguments: Trees.Variables.LIST
227
) ->
228
(type_expression: Trees.TypeExpressions.TypeExpression, function: Trees.Definitions.FUNCTION?)
229
is
230
let is_poisoned mut = false
231
let type_expression: Trees.TypeExpressions.TypeExpression mut
232
let function: Trees.Definitions.FUNCTION? mut = null
233
234
if context.current.token == Lexical.TOKEN.ARROW_THIN then
235
let arrow_location = context.location
236
context.next_token()
237
238
let use return_type_tokenizer_snapshot = context.tokenizer_speculate_then_commit()
239
240
let previous_in_return_type = context.in_return_type
241
242
context.in_return_type = true
243
244
try
245
type_expression = type_parser.parse(context)!
246
finally
247
context.in_return_type = previous_in_return_type
248
yrt
249
250
type_expression.check_is_not_reference(context.logger, "function cannot return a reference")
251
252
is_poisoned = is_poisoned \/ type_expression.is_poisoned
253
254
if context.current_token == Lexical.TOKEN.PAREN_OPEN then
255
return_type_tokenizer_snapshot.backtrack()
256
257
function =
258
Trees.Definitions.FUNCTION(
259
start::name!.location,
260
name,
261
generic_arguments,
262
arguments,
263
Trees.TypeExpressions.INFER(context.location),
264
Trees.Modifiers.LIST(context.location, null, null),
265
Trees.Bodies.NULL(context.location)
266
)
267
268
context.logger.error(arrow_location, "syntax error: incomplete return type")
269
270
return (type_expression, function)
271
fi
272
273
return_type_tokenizer_snapshot.commit()
274
else
275
type_expression = Trees.TypeExpressions.INFER(context.location)
276
fi
277
278
return (type_expression, function)
279
si
280
281
parse(context: CONTEXT) -> Trees.Definitions.FUNCTION is
282
if context.in_classy then
283
context.in_member = true
284
context.member_indent = context.location.start_column
285
else
286
context.in_global_function = true
287
context.global_indent = context.location.start_column
288
fi
289
290
try
291
let is_poisoned mut = false
292
let start = context.location
293
let name = identifier_function_name_parser.parse(context)
294
295
let generic_arguments = parse_generic_arguments(context)
296
297
is_poisoned = generic_arguments.is_poisoned
298
299
let af = parse_formal_arguments(context, start, name, generic_arguments)
300
301
if af.function? then
302
return af.function!
303
fi
304
305
let arguments = af.arguments
306
307
is_poisoned = is_poisoned \/ arguments.is_poisoned
308
309
let rt = parse_return_type(context, start, name, generic_arguments, arguments)
310
311
if rt.function? then
312
return rt.function!
313
fi
314
315
let type_expression = rt.type_expression
316
317
is_poisoned = is_poisoned \/ type_expression.is_poisoned
318
319
let modifiers = modifier_list_parser.parse(context)!
320
let expect_semicolon = context.current.token != Lexical.TOKEN.IS
321
322
let body: Trees.Bodies.Body mut = Trees.Bodies.NULL(context.location)
323
324
try
325
body = body_parser.parse(context)!
326
catch ue: UNWIND_TO_MEMBER_EXCEPTION
327
body = Trees.Bodies.NULL(context.location)
328
yrt
329
330
let result =
331
Trees.Definitions.FUNCTION(
332
start::body.location,
333
name,
334
generic_arguments,
335
arguments,
336
type_expression,
337
modifiers,
338
body
339
)
340
341
if expect_semicolon then
342
if !is_poisoned \/ context.current.token == Lexical.TOKEN.SEMICOLON then
343
is_poisoned = context.accept_terminator() == TERMINATOR.MISSING \/ is_poisoned
344
fi
345
fi
346
347
result.poison(is_poisoned)
348
349
return result
350
351
finally
352
if context.in_classy then
353
context.in_member = false
354
else
355
context.in_global_function = false
356
fi
357
yrt
358
si
359
si
360
si