Skip to content
← Back

src/syntax/process/printer/formatter_expressions.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
// Expression and member formatting: interpolation, variables, properties, modifiers,
11
// calls, parameter and argument lists.
12
partial FORMATTER is
13
14
// A range between two single terms reads tight: `1..10`, `a.b..c.e`,
15
// `(a + 1)..(b + 2)`. A term ends in a token that cannot join the
16
// range operator, so the spaces come off. Any other operand shape -
17
// a binary, a unary, an unwrap - keeps the spaced form.
18
// A binary chain is one breaking unit: the whole left-nested spine
19
// shares a single group with a soft break after each operator, so a
20
// chain that does not fit breaks one operand per line with each
21
// operator left at the end of the line above it. Breaks go after an
22
// operator only - a line-start operator opens something new, so a
23
// break before one would change what the next line re-parses as.
24
visit(binary: Expressions.BINARY) is
25
note(binary.location)
26
27
if is_tight_range(binary) then
28
binary.left.accept(self)
29
write_operation(binary)
30
binary.right.accept(self)
31
return
32
fi
33
34
_builder.begin_group()
35
_builder.begin_nest(4)
36
37
_emit_binary_chain(binary)
38
39
_builder.end_nest()
40
_builder.end_group()
41
si
42
43
// Emit a chain's operands and operators in tree order, a soft break
44
// after each operator. Only the left spine is flattened into the
45
// enclosing group; a binary right operand recurses through
46
// visit(BINARY) and breaks as its own unit, so a nested chain indents
47
// once rather than once per enclosing operator.
48
_emit_binary_chain(binary: Expressions.BINARY) is
49
if
50
let left_binary: Expressions.BINARY = cast Expressions.BINARY?(binary.left) /\
51
!is_tight_range(left_binary)
52
then
53
_emit_binary_chain(left_binary)
54
else
55
binary.left.accept(self)
56
fi
57
58
write(' ')
59
write_operation(binary)
60
_builder.line()
61
binary.right.accept(self)
62
si
63
64
write_operation(binary: Expressions.BINARY) is
65
if binary.actual_operation? then
66
write(binary.actual_operation)
67
else
68
binary.operation.accept(self)
69
fi
70
si
71
72
is_tight_range(binary: Expressions.BINARY) -> bool is
73
let name = binary.actual_operation ?? binary.operation.name
74
75
if !(name.starts_with("..") \/ name.starts_with("::")) then
76
return false
77
fi
78
79
return is_range_term(binary.left) /\ is_range_term(binary.right)
80
si
81
82
// An operand that needs no separating space beside a range operator:
83
// it ends in a name character or a closing delimiter.
84
is_range_term(e: Expressions.Expression) -> bool =>
85
isa Expressions.Literals.Literal(e) \/
86
isa Expressions.IDENTIFIER(e) \/
87
isa Expressions.MEMBER(e) \/
88
isa Expressions.CALL(e) \/
89
isa Expressions.INDEX(e) \/
90
isa Expressions.TUPLE(e)
91
92
// --- faithfulness fix: string interpolation. The inherited visit
93
// runs each literal fragment through visit(STRING), which wraps
94
// it in quotes — producing invalid nested-string output. Emit the
95
// literal fragments as raw escaped text instead. ---
96
97
visit(interpolation: Expressions.STRING_INTERPOLATION) is
98
note(interpolation.location)
99
100
write(cast char(34))
101
102
for fragment in interpolation.values do
103
if fragment.is_expression then
104
write('{')
105
fragment.expression.accept(self)
106
if let fragment.alignment? then
107
write(',')
108
alignment.accept(self)
109
fi
110
if fragment.format? then
111
let format = fragment.format!
112
write(':')
113
write(format)
114
fi
115
write('}')
116
else
117
emit_interpolation_literal(fragment)
118
fi
119
od
120
121
write(cast char(34))
122
si
123
124
emit_interpolation_literal(fragment: Expressions.INTERPOLATION_FRAGMENT) is
125
let literal = cast Expressions.Literals.STRING?(fragment.expression)
126
127
if !literal? then
128
return
129
fi
130
131
for c in literal.value_string do
132
if c == '{' then
133
write('{')
134
write('{')
135
elif c == '}' then
136
write('}')
137
write('}')
138
else
139
write_escape_char(c)
140
fi
141
od
142
si
143
144
// --- faithfulness fix: the inherited write_escape_char renders
145
// control characters via string.format("X", ci), which returns
146
// the literal "X" (no placeholder), mangling e.g. '\n' to '\X'.
147
// Emit the escapes the tokenizer actually understands. ---
148
149
write_escape_char(c: char) is
150
let ci = cast int(c)
151
152
if c == '\n' then
153
write("\\")
154
write('n')
155
elif ci == 9 then
156
write("\\")
157
write('t')
158
elif ci == 13 then
159
write("\\")
160
write('r')
161
elif ci == 34 then
162
write("\\")
163
write(cast char(34))
164
elif ci == 92 then
165
write("\\\\")
166
elif ci < 32 then
167
write("\\u00")
168
write(hex_digit((ci >> 4) & 15))
169
write(hex_digit(ci & 15))
170
else
171
write(c)
172
fi
173
si
174
175
hex_digit(n: int) -> char is
176
if n < 10 then
177
return cast char(cast int('0') + n)
178
fi
179
180
return cast char(cast int('A') + n - 10)
181
si
182
183
// --- faithfulness fix: a plain string literal loses its brace
184
// escaping. `{{` written back as `{` opens an interpolation on
185
// the next parse. The interpolated path doubles braces
186
// (emit_interpolation_literal); the plain path must agree. ---
187
188
visit(string: Expressions.Literals.STRING) is
189
note(string.location)
190
write(cast char(34))
191
192
for c in string.value_string do
193
if c == '{' then
194
write('{')
195
write('{')
196
elif c == '}' then
197
write('}')
198
write('}')
199
else
200
write_escape_char(c)
201
fi
202
od
203
204
write(cast char(34))
205
si
206
207
// --- faithfulness fix: a character literal's quote must stay
208
// escaped - `'\''` written back as `'''` is reported as a
209
// zero-length literal - and a double quote needs no escape in
210
// this context, where the inherited write_escape_char (which
211
// carries the string literal's rules) adds one. ---
212
213
visit(character: Expressions.Literals.CHARACTER) is
214
note(character.location)
215
write("'")
216
217
let c = character.value_string[0]
218
219
if c == '\'' then
220
write("\\'")
221
elif c == cast char(34) then
222
write(c)
223
else
224
write_escape_char(c)
225
fi
226
227
write("'")
228
si
229
230
// --- faithfulness fix: a prefix operator is written tight against
231
// its operand. The inherited visit spaces it (`! x`), which is
232
// a faithfulness change on every reformat; a line-start
233
// operator is significant to boundary inference, so the
234
// spacing must not drift. ---
235
236
visit(unary: Expressions.UNARY) is
237
note(unary.location)
238
unary.operation.accept(self)
239
unary.right.accept(self)
240
si
241
242
// --- faithfulness fix: the inherited expression-list emitter
243
// writes `,` with no following space, so a tuple literal comes
244
// back as `(a,b)`. ---
245
246
visit(expressions: Expressions.LIST) is
247
note(expressions.location)
248
249
let seen_any mut = false
250
251
for e in expressions do
252
if seen_any then
253
write(", ")
254
fi
255
e.accept(self)
256
seen_any = true
257
od
258
259
if expressions.has_trailing_comma then
260
write(',')
261
fi
262
si
263
264
// --- faithfulness fix: a postfix type constructor applied to a
265
// function type binds to that function's *result*, so
266
// `(() -> T)?` written back without its parens is `() -> T?`,
267
// a function returning an optional. Parenthesise the operand
268
// whenever it is a function type. ---
269
270
visit(optional: TypeExpressions.OPTIONAL) is
271
note(optional.location)
272
emit_type_operand(optional.element)
273
write("?")
274
si
275
276
visit(array: TypeExpressions.ARRAY_) is
277
note(array.location)
278
emit_type_operand(array.element)
279
write("[]")
280
si
281
282
visit(pointer: TypeExpressions.POINTER) is
283
note(pointer.location)
284
emit_type_operand(pointer.element)
285
write(" ptr")
286
si
287
288
visit(reference: TypeExpressions.REFERENCE) is
289
note(reference.location)
290
emit_type_operand(reference.element)
291
write(" ref")
292
si
293
294
emit_type_operand(element: TypeExpressions.TypeExpression) is
295
if isa TypeExpressions.FUNCTION(element) then
296
write("(")
297
element.accept(self)
298
write(")")
299
return
300
fi
301
302
element.accept(self)
303
si
304
305
// --- faithfulness fix: a VARIABLE carries three optional pieces
306
// the inherited printer never emits — the `..` splice marker
307
// (only inside a secondary `init` formal-arg list), the
308
// trailing `mut` keyword (set by the parser when source had
309
// `mut`), and a trailing modifier suffix list (only on
310
// primary-ctor parameter declarations, where `public`,
311
// `field`, and `init` describe the auto-generated body
312
// member). ---
313
314
// A lambda parameter written as a destructure group has a synthesised
315
// name - the source names only the leaves - and the group itself is on
316
// `left`. Printing the name hands back `$destructured_argument`, which
317
// is not what was written and does not parse.
318
visit(variable: Expressions.VARIABLE) is
319
emit_parameter_pragmas(variable.pragmas)
320
321
if variable.is_synthesized /\ variable.left? then
322
variable.left.accept(self)
323
else
324
variable.name.accept(self)
325
fi
326
327
let type_expression = variable.type_expression
328
329
if !isa TypeExpressions.INFER(type_expression) /\ !_strip("parameter", variable.location, "type", type_expression.location, type_expression.location) then
330
write(": ")
331
type_expression.accept(self)
332
elif let annotation = _annotation(a => a.parameter_type(variable.location)) then
333
write(": {annotation}")
334
fi
335
336
if !variable.is_synthesized /\ variable.initializer? then
337
write(" = ")
338
variable.initializer!.accept(self)
339
fi
340
si
341
342
visit(variable: Variables.VARIABLE) is
343
note(variable.location)
344
345
if variable.is_splice then
346
write("..")
347
return
348
fi
349
350
emit_parameter_pragmas(variable.pragmas)
351
352
variable.left.accept(self)
353
354
let variable_type_expression = variable.type_expression
355
356
if !isa TypeExpressions.INFER(variable_type_expression) /\ !(_in_let /\ _strip("local", variable.location, "type", variable_type_expression.location, variable_type_expression.location)) then
357
write(": ")
358
variable_type_expression.accept(self)
359
elif let annotation = _annotation(a => a.local_type(variable.location)) then
360
write(": {annotation}")
361
fi
362
363
if variable.is_mutable_marked then
364
write(" mut")
365
fi
366
367
if let variable.modifiers? /\ !modifiers.is_empty then
368
write(" ")
369
emit_modifiers(modifiers)
370
fi
371
372
let initializer = variable.initializer
373
if initializer? then
374
write(" = ")
375
initializer.accept(self)
376
fi
377
si
378
379
// --- faithfulness fix: a body-less property (a field) is terminated
380
// with `;` and a newline, which the plain printer omits ---
381
382
visit(property: Definitions.PROPERTY) is
383
if property.name? then
384
property.name.accept(self)
385
fi
386
387
let property_type_expression = property.type_expression
388
389
if !isa TypeExpressions.INFER(property_type_expression) then
390
write(": ")
391
property_type_expression.accept(self)
392
fi
393
394
if !property.modifiers.is_empty then
395
write(" ")
396
emit_modifiers(property.modifiers)
397
fi
398
399
if !property.read_body? /\ !property.assign_body? then
400
write_terminator()
401
return
402
fi
403
404
let out_again =
405
indent_property(
406
property.read_body? /\ !_has_no_written_body(property.read_body),
407
property.assign_body?
408
)
409
410
if property.read_body? then
411
property.read_body.accept(self)
412
if property.assign_body? then
413
write_line(",")
414
else
415
after_body(property.read_body!)
416
fi
417
else
418
write(" ")
419
fi
420
421
if property.assign_body? then
422
write("= ")
423
property.assign_argument!.accept(self)
424
// The block body opens with `is`, written directly after
425
// whatever precedes it. The argument does not end in a
426
// space, so without one here the keyword runs onto it -
427
// `= valueis` - and the result no longer parses.
428
if !at_line_start then
429
write(" ")
430
fi
431
property.assign_body!.accept(self)
432
after_body(property.assign_body!)
433
fi
434
435
if out_again then
436
outdent()
437
fi
438
si
439
440
// Emit modifiers without the trailing space the inherited
441
// Modifiers.LIST visit appends (which would strand a space before a
442
// following `;`).
443
emit_modifiers(modifiers: Modifiers.LIST) is
444
let first mut = true
445
446
if let modifiers.access_modifier? then
447
access_modifier.accept(self)
448
first = false
449
fi
450
451
if let modifiers.storage_class? then
452
if !first then
453
write(" ")
454
fi
455
storage_class.accept(self)
456
first = false
457
fi
458
459
if modifiers.is_abstract then
460
if !first then
461
write(" ")
462
fi
463
write("abstract")
464
first = false
465
fi
466
467
if modifiers.is_pure then
468
if !first then
469
write(" ")
470
fi
471
write("pure")
472
first = false
473
fi
474
475
if modifiers.is_stable then
476
if !first then
477
write(" ")
478
fi
479
write("stable")
480
first = false
481
fi
482
si
483
484
// --- wrapping: call argument lists and function parameter lists ---
485
486
visit(call: Expressions.CALL) is
487
note(call.location)
488
if call.is_thread_first /\ call.arguments.count >= 1 then
489
call.arguments.expressions[0].accept(self)
490
write(if call.propagates_absence then " ~> " else " |> " fi)
491
emit_callee(call.function)
492
emit_inferred_type_arguments(call.function)
493
write("(")
494
emit_argument_list(call.arguments, 1)
495
write(")")
496
else
497
emit_callee(call.function)
498
emit_inferred_type_arguments(call.function)
499
write("(")
500
emit_argument_list(call.arguments, 0, call.argument_names)
501
write(")")
502
fi
503
si
504
505
// A callee that writes its own type arguments prints without
506
// them when the stripper takes that site.
507
// A single type argument parses as an index until the build
508
// decides, so that shape arrives as the ambiguous node.
509
emit_callee(callee: Expressions.Expression) is
510
if let generic: Expressions.GENERIC_APPLICATION = callee /\ _strip("type-arguments", callee.location, "after", generic.identifier.location, callee.location) then
511
if let generic.left? then
512
left.accept(self)
513
write(".")
514
fi
515
516
generic.identifier.accept(self)
517
elif let ambiguous: Expressions.AMBIGUOUS_EXPRESSION = callee /\ _names_a_type(_bracketed(ambiguous.index)) /\ _strip("type-arguments", callee.location, "after", ambiguous.identifier.location, callee.location) then
518
if let ambiguous.left? then
519
left.accept(self)
520
write(".")
521
fi
522
523
ambiguous.identifier.accept(self)
524
else
525
callee.accept(self)
526
fi
527
si
528
529
// What an ambiguous bracket holds: the node keeps the whole indexing
530
// expression, whose own index is the bracket's content.
531
_bracketed(index: Expressions.Expression) -> Expressions.Expression =>
532
if let indexed: Expressions.INDEX = index then indexed.index else index fi
533
534
// Whether an ambiguous bracket's content is a type rather than an
535
// index: a capitalised or qualified name, or a primitive's.
536
_names_a_type(index: Expressions.Expression) -> bool is
537
if let identifier: Expressions.IDENTIFIER = index then
538
let name = identifier.identifier.name
539
540
char.is_upper(name[0]) \/
541
["int", "string", "bool", "double", "char", "object", "single", "long", "byte", "short", "decimal", "bigint", "uint", "ulong", "ubyte", "ushort", "word", "uword", "void"]
542
|> Ghul.Pipes.any(p => p =~ name)
543
elif isa Expressions.MEMBER(index) then
544
true
545
else
546
false
547
fi
548
si
549
550
emit_inferred_type_arguments(callee: Expressions.Expression) is
551
// Only a bare or qualified name: a callee that writes its own
552
// type arguments parses as something else.
553
if !isa Expressions.IDENTIFIER(callee) /\ !isa Expressions.MEMBER(callee) then
554
return
555
fi
556
557
if let arguments = _annotation(a => a.type_arguments(callee.location)) then
558
write("[")
559
write(arguments)
560
write("]")
561
fi
562
si
563
564
visit(function: Definitions.FUNCTION) is
565
if let function.name? then
566
name.accept(self)
567
fi
568
569
if _has_type_arguments(function.generic_arguments) then
570
write("[")
571
function.generic_arguments.accept(self)
572
write("]")
573
fi
574
575
write("(")
576
emit_parameter_list(function.arguments)
577
write(")")
578
579
let function_type_expression = function.type_expression
580
581
if !isa TypeExpressions.INFER(function_type_expression) then
582
write(" -> ")
583
function_type_expression.accept(self)
584
fi
585
586
if !function.modifiers.is_empty then
587
write(" ")
588
emit_modifiers(function.modifiers)
589
fi
590
591
let body = function.body
592
593
if body? /\ !_has_no_written_body(body) then
594
write(" ")
595
body.accept(self)
596
fi
597
598
after_body(body)
599
si
600
601
emit_parameter_list(parameters: Variables.LIST) is
602
if !_has_parameters(parameters) then
603
return
604
fi
605
606
_builder.begin_group()
607
_builder.begin_nest(4)
608
_builder.soft_line()
609
610
let first mut = true
611
for p in parameters do
612
if !first then
613
_builder.text(",")
614
_builder.line()
615
fi
616
p.accept(self)
617
first = false
618
od
619
620
_builder.end_nest()
621
_builder.soft_line()
622
_builder.end_group()
623
si
624
625
_has_parameters(parameters: Variables.LIST?) -> bool is
626
if !parameters? then
627
return false
628
fi
629
for p in parameters do
630
return true
631
od
632
return false
633
si
634
635
_has_type_arguments(arguments: TypeExpressions.LIST?) -> bool is
636
if !arguments? then
637
return false
638
fi
639
for a in arguments do
640
return true
641
od
642
return false
643
si
644
645
// --- faithfulness fix: a NAMED_TUPLE_ELEMENT in type-parameter
646
// position can carry up to four pieces of constraint syntax
647
// past its name:type slot — the trailing kind keyword after a
648
// type bound (`[T: A class]`), an `init` ctor constraint, and a
649
// trailing `in` / `out` variance. The inherited visit emits
650
// only `name: type_expression`, dropping the other three.
651
// The `[T: out]` variance-only form also needs its
652
// TYPE_PARAMETER_CONSTRAINT(NONE) placeholder suppressed
653
// (the inherited path renders it as `?`). ---
654
655
visit(element: TypeExpressions.NAMED_TUPLE_ELEMENT) is
656
note(element.location)
657
element.name.accept(self)
658
write(": ")
659
660
let constraint = cast TypeExpressions.TYPE_PARAMETER_CONSTRAINT?(element.type_expression)
661
let is_placeholder =
662
constraint? /\
663
constraint.kind == Semantic.Symbols.TypeParameterConstraintKind.NONE
664
let need_separator mut = false
665
666
if !is_placeholder then
667
element.type_expression.accept(self)
668
need_separator = true
669
fi
670
671
for bound in element.additional_bounds do
672
write(" /\\ ")
673
bound.accept(self)
674
od
675
676
if element.combined_kind != Semantic.Symbols.TypeParameterConstraintKind.NONE then
677
if need_separator then
678
write(" ")
679
fi
680
write(_constraint_kind_keyword(element.combined_kind))
681
need_separator = true
682
fi
683
684
if element.has_constructor then
685
if need_separator then
686
write(" ")
687
fi
688
write("init")
689
need_separator = true
690
fi
691
692
if element.variance == Semantic.Types.TypeVariance.COVARIANT then
693
if need_separator then
694
write(" ")
695
fi
696
write("out")
697
elif element.variance == Semantic.Types.TypeVariance.CONTRAVARIANT then
698
if need_separator then
699
write(" ")
700
fi
701
write("in")
702
fi
703
si
704
705
_constraint_kind_keyword(kind: Semantic.Symbols.TypeParameterConstraintKind) -> string is
706
if kind == Semantic.Symbols.TypeParameterConstraintKind.REFERENCE then
707
return "class"
708
elif kind == Semantic.Symbols.TypeParameterConstraintKind.VALUE then
709
return "struct"
710
elif kind == Semantic.Symbols.TypeParameterConstraintKind.OPTIONAL then
711
return "optional"
712
fi
713
return ""
714
si
715
716
emit_argument_list(arguments: Expressions.LIST) is
717
emit_argument_list(arguments, 0)
718
si
719
720
emit_argument_list(arguments: Expressions.LIST, start: int) is
721
emit_argument_list(arguments, start, null)
722
si
723
724
// Attributes written before a parameter stay on its own line.
725
emit_parameter_pragmas(pragmas: Collections.LIST[Pragmas.PRAGMA]?) is
726
if !pragmas? then
727
return
728
fi
729
730
for pragma in pragmas do
731
pragma.accept(self)
732
write(" ")
733
od
734
si
735
736
// `names`, when the call used named-argument syntax, runs parallel
737
// to `arguments`: the parser moved each value into the list and
738
// kept the name beside it.
739
emit_argument_list(arguments: Expressions.LIST, start: int, names: Collections.List[Identifiers.Identifier]?) is
740
if arguments.count <= start then
741
return
742
fi
743
744
_builder.begin_group()
745
_builder.begin_nest(4)
746
_builder.soft_line()
747
748
let first mut = true
749
for i in start..arguments.count do
750
if !first then
751
_builder.text(",")
752
_builder.line()
753
fi
754
755
if names? /\ i < names.count then
756
names[i].accept(self)
757
write(" = ")
758
fi
759
760
arguments.expressions[i].accept(self)
761
first = false
762
od
763
764
// Written hard against the argument it trails, inside the nest,
765
// so a broken layout puts it at the end of that argument's line
766
// and leaves the closing bracket on its own.
767
if arguments.has_trailing_comma then
768
_builder.text(",")
769
fi
770
771
_builder.end_nest()
772
_builder.soft_line()
773
_builder.end_group()
774
si
775
776
_has_arguments(arguments: Expressions.LIST) -> bool is
777
for a in arguments do
778
return true
779
od
780
return false
781
si
782
si
783
si