Skip to content
← Back

src/syntax/process/printer/formatter_definitions.ghul

1
namespace Syntax.Process.Printer is
2
use Collections
3
4
use Trees
5
use Source
6
7
use Lexical.TRIVIA
8
9
10
// Definition formatting: function literals and declarations, classes, structs, partial and
11
// impl blocks, unions and variants.
12
partial FORMATTER is
13
14
// --- faithfulness fix: a lambda's argument list must be parenthesised
15
// unless it is a single argument with an inferred return type —
16
// the only paren-free lambda form. A bare empty list (`=> body`)
17
// or a bare multi-argument list does not reparse as a lambda, and
18
// an explicit return type needs the parens or `arg: T -> U`
19
// reparses as an argument of function type `T -> U`. ---
20
21
visit(function: Expressions.FUNCTION) is
22
note(function.location)
23
24
let return_annotation =
25
if isa TypeExpressions.INFER(function.type_expression) then
26
_annotation(a => a.return_type(function.location))
27
else
28
null
29
fi
30
31
let explicit_return =
32
(!isa TypeExpressions.INFER(function.type_expression) /\ !_strip("return", function.location, "type", function.type_expression.location, function.type_expression.location)) \/ return_annotation?
33
34
if let function.nested_name? then
35
nested_name.accept(self)
36
fi
37
38
// The paren-free form is available only for a sole argument that is
39
// a bare name. A destructure group renders as its own parenthesised
40
// pattern, so dropping the argument list's parens would leave that
41
// pattern standing as the list - `((a, b)) => ...`, one destructured
42
// argument, reparsing as `(a, b) => ...`, which is two. An argument
43
// with a written type needs them too: `word: string => ...` does not
44
// parse as a lambda at all.
45
let sole_argument_needs_parens =
46
function.arguments.count == 1 /\
47
(function.arguments
48
|> Ghul.Pipes.any(
49
a =>
50
if let only = cast Expressions.VARIABLE?(a) then
51
(only.is_synthesized /\ only.left?) \/
52
!isa TypeExpressions.INFER(only.type_expression) \/
53
_annotation(a => a.parameter_type(only.location))?
54
else
55
_annotation(x => x.parameter_type(a.location))?
56
fi
57
))
58
59
// A named nested function always writes its argument list in
60
// parens: the paren-free lambda form has nowhere to put the name.
61
let parenthesise =
62
function.nested_name? \/
63
explicit_return \/
64
function.arguments.count != 1 \/
65
sole_argument_needs_parens
66
67
if parenthesise then
68
write("(")
69
fi
70
71
let first mut = true
72
for a in function.arguments do
73
if !first then
74
write(", ")
75
fi
76
77
a.accept(self)
78
79
// A parameter written as a bare name parses as an
80
// identifier rather than a variable; its inferred type
81
// is written after it the same way.
82
if isa Expressions.IDENTIFIER(a) then
83
if let annotation = _annotation(x => x.parameter_type(a.location)) then
84
write(": {annotation}")
85
fi
86
fi
87
88
first = false
89
od
90
91
if parenthesise then
92
write(")")
93
fi
94
95
if let annotation = return_annotation then
96
write(" -> {annotation}")
97
elif explicit_return then
98
write(" -> ")
99
function.type_expression.accept(self)
100
fi
101
102
// The marker follows the return type: `(n: int) -> int rec`.
103
// `rec` names the literal from inside its own body. Dropping it
104
// leaves every self-reference in the body unresolved. A named
105
// nested function is reached by its name instead, so its own
106
// recursion marker is never written.
107
if function.is_recursive /\ !function.nested_name? then
108
write(" rec")
109
fi
110
111
write(" ")
112
function.body.accept(self)
113
si
114
115
// --- faithfulness fix: the inherited visits render a pragma's
116
// subject and drop the pragma itself, so `--format-in-place`
117
// silently deletes every attribute and every conditional-
118
// compilation guard in the file. A dropped `@IF.debug()` also
119
// unguards the statement it was written on. ---
120
121
visit(pragma: Pragmas.PRAGMA) is
122
note(pragma.location)
123
124
write("@")
125
pragma.name.accept(self)
126
write("(")
127
128
let first mut = true
129
130
for argument in pragma.arguments do
131
if !first then
132
write(", ")
133
fi
134
argument.accept(self)
135
first = false
136
od
137
138
if let pragma.named_arguments? then
139
for named_argument in named_arguments do
140
if !first then
141
write(", ")
142
fi
143
named_argument.name.accept(self)
144
write(" = ")
145
named_argument.value.accept(self)
146
first = false
147
od
148
fi
149
150
write(")")
151
si
152
153
// An attribute on a definition sits on its own line above it.
154
visit(pragma: Definitions.PRAGMA) is
155
note(pragma.location)
156
pragma.pragma.accept(self)
157
write_line()
158
pragma.definition.accept(self)
159
si
160
161
// A pragma on a statement stays on the statement's own line.
162
visit(pragma: Statements.PRAGMA) is
163
note(pragma.location)
164
pragma.pragma.accept(self)
165
166
if let pragma.statement? then
167
write(" ")
168
statement.accept(self)
169
fi
170
si
171
172
// --- faithfulness fix: an unresolved AMBIGUOUS_EXPRESSION (a parse-
173
// time `x[y]` that could be a generic application or an index)
174
// has the same surface syntax either way. The plain printer emits
175
// a debug `(ambiguous ... or ...)` form, which is not valid ghūl. ---
176
177
visit(ambiguous: Expressions.AMBIGUOUS_EXPRESSION) is
178
note(ambiguous.location)
179
180
if let ambiguous.left? then
181
left.accept(self)
182
write(".")
183
fi
184
185
ambiguous.identifier.accept(self)
186
write("[")
187
ambiguous.type_arguments.accept(self)
188
write("]")
189
si
190
191
// --- faithfulness fix: STRUCT printed as `struct`, not `trait`;
192
// CLASS / STRUCT carry an optional primary-constructor header
193
// `(params)` between the type-argument list and the ancestor
194
// list, and a body-less primary-ctor form `class X(p: T);` is
195
// emitted as the `;` shorthand rather than `is si`. ---
196
197
visit(`class: Definitions.CLASS) is
198
emit_classy("class", `class)
199
si
200
201
visit(`struct: Definitions.STRUCT) is
202
emit_classy("struct", `struct)
203
si
204
205
// The inherited visit writes a trait's modifiers straight after its
206
// name, so `trait T open` comes back as `trait Topen`. emit_classy
207
// separates them, and handles the ancestor clause the same way.
208
visit(`trait: Definitions.TRAIT) is
209
emit_classy("trait", `trait)
210
si
211
212
// `partial Target[args] is … si` — no ancestor clause, so emit_classy
213
// (which only writes `: …` when ancestors are present) prints it faithfully.
214
visit(`partial: Definitions.PARTIAL) is
215
emit_classy("partial", `partial)
216
si
217
218
// `impl Interface for Target[args] is … si` — the interface leads and the
219
// target follows `for`, the reverse of the `Target: Interface` shape
220
// emit_classy assumes, so it needs its own emitter.
221
visit(`impl: Definitions.IMPL) is
222
note(`impl.location)
223
224
write("impl ")
225
226
if `impl.ancestors? then
227
`impl.ancestors.accept(self)
228
fi
229
230
write(" for ")
231
`impl.name.accept(self)
232
233
if `impl.arguments? then
234
write("[")
235
`impl.arguments!.accept(self)
236
write("]")
237
fi
238
239
if !`impl.modifiers.is_empty then
240
write(" ")
241
emit_modifiers(`impl.modifiers)
242
fi
243
244
write_line(" is")
245
indent()
246
`impl.body.accept(self)
247
flush_leading(`impl.body.location.end)
248
outdent()
249
write_line("si")
250
si
251
252
emit_classy(keyword: string, classy: Definitions.Classy) is
253
note(classy.location)
254
255
write(keyword)
256
write(" ")
257
classy.name.accept(self)
258
259
if classy.arguments? then
260
write("[")
261
classy.arguments!.accept(self)
262
write("]")
263
fi
264
265
if classy.primary_params? then
266
let primary_params = classy.primary_params
267
write("(")
268
emit_parameter_list(primary_params)
269
write(")")
270
fi
271
272
if classy.ancestors? then
273
write(": ")
274
classy.ancestors!.accept(self)
275
fi
276
277
if !classy.modifiers.is_empty then
278
write(" ")
279
emit_modifiers(classy.modifiers)
280
fi
281
282
if classy.primary_params? /\ _body_is_empty(classy.body) then
283
write_terminator()
284
return
285
fi
286
287
write_line(" is")
288
indent()
289
classy.body.accept(self)
290
291
// Up to the class's own end, not its body's: a blank line or a
292
// comment written after the last definition sits between the
293
// two, and flushing only to the body's end leaves it in the
294
// queue to surface after the `si` - which reads as one more
295
// blank line between this class and the next, and as another
296
// on every reformat.
297
flush_leading(classy.location.end)
298
299
outdent()
300
write_line("si")
301
si
302
303
_body_is_empty(body: Definitions.LIST) -> bool is
304
for d in body do
305
return false
306
od
307
return true
308
si
309
310
// --- faithfulness fix: SUPER_CALL is a body declaration only
311
// reachable inside a primary-constructor class/struct body.
312
// The base printer's StrictVisitor base throws on unhandled
313
// node types — provide an explicit visit. ---
314
315
visit(super_call: Definitions.SUPER_CALL) is
316
note(super_call.location)
317
write("super(")
318
let first mut = true
319
for a in super_call.args do
320
if !first then
321
write(", ")
322
fi
323
a.accept(self)
324
first = false
325
od
326
write(")")
327
write_terminator()
328
si
329
330
// --- faithfulness fix: the base printer's UNION visit ignores
331
// the primary-constructor header (the parens after the union
332
// name) and so drops it on round-trip. Emit it. ---
333
334
visit(`union: Definitions.UNION) is
335
note(`union.location)
336
337
write("union ")
338
`union.name.accept(self)
339
340
if `union.arguments? then
341
write("[")
342
`union.arguments!.accept(self)
343
write("]")
344
fi
345
346
if `union.primary_params? then
347
let primary_params = `union.primary_params
348
write("(")
349
emit_parameter_list(primary_params)
350
write(")")
351
fi
352
353
if `union.ancestors? /\ `union.ancestors.count > 0 then
354
write(": ")
355
`union.ancestors!.accept(self)
356
fi
357
358
if !`union.modifiers.is_empty then
359
write(" ")
360
emit_modifiers(`union.modifiers)
361
fi
362
363
write_line(" is")
364
indent()
365
366
let previous_has_primary = _union_has_primary_params
367
_union_has_primary_params = `union.primary_params?
368
369
try
370
`union.body.accept(self)
371
finally
372
_union_has_primary_params = previous_has_primary
373
yrt
374
375
flush_leading(`union.body.location.end)
376
outdent()
377
write_line("si")
378
si
379
380
// --- faithfulness fix: a typed-union VARIANT is parsed with a
381
// synthetic `public field` modifier list and an always-empty
382
// body block, so the inherited visit prints
383
// `RED public field is\nsi`. A variant has no user-written
384
// body and the modifiers are not source-visible — emit the
385
// `name(fields)? default? ;` form the user actually wrote. ---
386
387
visit(variant: Definitions.VARIANT) is
388
note(variant.location)
389
390
variant.name.accept(self)
391
392
if variant.fields.count > 0 /\ !_variant_fields_are_implicit_splice(variant) then
393
write("(")
394
variant.fields.accept(self)
395
write(")")
396
fi
397
398
if variant.is_default then
399
write(" default")
400
fi
401
402
write_terminator()
403
si
404
405
// True when the variant's only field is a single `..` splice and
406
// we are inside a primary-header union — the splice is then
407
// implied by the bare `NAME;` form and the parens add noise.
408
_variant_fields_are_implicit_splice(variant: Definitions.VARIANT) -> bool is
409
if !_union_has_primary_params then
410
return false
411
fi
412
413
if variant.fields.count != 1 then
414
return false
415
fi
416
417
for v in variant.fields do
418
return v.is_splice
419
od
420
421
return false
422
si
423
si
424
si