Skip to content
← Back

src/syntax/process/printer/formatter.ghul

1
namespace Syntax.Process.Printer is
2
use Collections;
3
4
use Trees;
5
use Source;
6
7
use Lexical.TRIVIA;
8
9
// The ghūl code formatter.
10
//
11
// Extends the plain GHUL printer (which the diagnostics path uses via
12
// Node.to_string and must not be disturbed) and redirects its output
13
// primitives into a DOC tree, so DOC_RENDERER can lay the result out
14
// against a column budget. Comments and blank lines collected by the
15
// tokenizer as TRIVIA are interleaved back in by source position.
16
//
17
// Selected visits are overridden to introduce soft breaks (GROUP/LINE)
18
// where wrapping should happen; everything else inherits the printer's
19
// structural layout unchanged.
20
class FORMATTER: GHUL is
21
_builder: DOC_BUILDER;
22
_width: int;
23
24
_trivia: LIST[TRIVIA];
25
_trivia_index: int;
26
27
// Source line of the furthest content emitted so far. Drives
28
// trailing-comment detection: a comment starting on a line we have
29
// already passed trails that line rather than leading the next node.
30
_last_line: int;
31
32
// True between opening a block and emitting its first real content;
33
// suppresses a blank line immediately inside a block.
34
_at_block_start: bool;
35
36
// True while inside a union that declared a primary-constructor
37
// header. Variants emitted under this flag can drop a sole
38
// `(..)` field list — the rewriter implicitly splices the primary
39
// params for a variant with no field list. Without a primary
40
// header, a sole `..` is a hard error, and dropping the parens
41
// would silently turn the error into valid empty-fields source.
42
_union_has_primary_params: bool;
43
44
init(trivia: Iterable[TRIVIA]?, width: int) is
45
super.init();
46
47
_width = width;
48
_builder = DOC_BUILDER();
49
50
_trivia = LIST[TRIVIA]();
51
if trivia? then
52
for t in trivia do
53
_trivia.add(t);
54
od
55
fi
56
si
57
58
// Format a parsed file (or any node) and return the rendered text.
59
format(node: Node) -> string is
60
node.accept(self);
61
flush_remaining();
62
63
let text = DOC_RENDERER(_width).render(_builder.build());
64
65
// Strip trailing whitespace from every line (blank lines pick up
66
// indentation from the renderer) and end with a single newline.
67
let result = System.Text.StringBuilder();
68
let first mut = true;
69
for line in text.split(['\n']) do
70
if !first then
71
result.append('\n');
72
fi
73
result.append(line.trim_end());
74
first = false;
75
od
76
77
return "{result.to_string().trim_end()}\n";
78
si
79
80
// --- output primitives, redirected into the DOC builder ---
81
82
write(value: string?) is
83
if !value? then
84
return;
85
fi
86
87
if value.length > 0 then
88
_at_block_start = false;
89
fi
90
91
_builder.text(value);
92
si
93
94
write(c: char) is
95
_at_block_start = false;
96
_builder.text("{c}");
97
si
98
99
// A name that collides with a keyword, or that would tokenise as a
100
// number, only reached the tree via a backtick escape; the inherited
101
// printer drops the backtick and the result no longer re-parses.
102
// Restore it. Operator names are left to visit(Expressions.IDENTIFIER):
103
// they must stay bare in definition and binary-operation positions,
104
// which route through here too.
105
write_name(name: string) is
106
if Lexical.TOKENIZER.is_reserved_word(name) \/ Lexical.TOKENIZER.is_numeric_identifier(name) then
107
write('`');
108
fi
109
write(name);
110
si
111
112
// An operator referred to as a plain value, rather than applied,
113
// reached the tree via a backtick escape and must keep it to re-parse
114
// as an identifier rather than an operator. This is the one identifier
115
// position where an operator escapes; definition names and binary
116
// operations render the operator bare through the inherited visits.
117
visit(identifier: Expressions.IDENTIFIER) is
118
location(identifier);
119
let inner = identifier.identifier;
120
if !inner.is_qualified /\ Lexical.TOKENIZER.is_operator_name(inner.name) then
121
write('`');
122
write(inner.name);
123
else
124
inner.accept(self);
125
fi
126
si
127
128
write_line() is
129
flush_trailing_comments();
130
_builder.hard_line();
131
si
132
133
indent() is
134
_builder.begin_nest(4);
135
_at_block_start = true;
136
si
137
138
outdent() is
139
_builder.end_nest();
140
si
141
142
write_indent() is si
143
144
// --- line tracking ---
145
146
location(location: LOCATION) is
147
note(location);
148
si
149
150
note(location: LOCATION?) is
151
if location? /\ location.start_line > _last_line then
152
_last_line = location.start_line;
153
fi
154
si
155
156
// --- trivia interleaving ---
157
158
_has_trivia: bool => _trivia_index < _trivia.count;
159
160
_peek: TRIVIA => _trivia[_trivia_index];
161
162
_consume() is
163
_trivia_index = _trivia_index + 1;
164
si
165
166
// Emit any trivia positioned strictly before `limit` (a LOCATION.start
167
// value) that is not a trailing comment of an already-emitted line.
168
flush_leading(limit: int) is
169
while _has_trivia /\ _peek.location.start < limit do
170
let t = _peek;
171
172
if isa TRIVIA.BLANK_LINE(t) then
173
if !_at_block_start then
174
_builder.hard_line();
175
fi
176
_consume();
177
else
178
emit_comment(t);
179
_builder.hard_line();
180
_at_block_start = false;
181
_consume();
182
fi
183
od
184
si
185
186
// Emit comments that start on a line we have already emitted: they
187
// trail that line. Called from write_line, before the newline.
188
flush_trailing_comments() is
189
while
190
_has_trivia /\
191
_is_comment(_peek) /\
192
_peek.location.start_line <= _last_line
193
do
194
_builder.text(" ");
195
emit_comment(_peek);
196
_consume();
197
od
198
si
199
200
// Emit everything left over (file-trailing comments).
201
flush_remaining() is
202
while _has_trivia do
203
let t = _peek;
204
if _is_comment(t) then
205
emit_comment(t);
206
_builder.hard_line();
207
fi
208
_consume();
209
od
210
si
211
212
_is_comment(t: TRIVIA) -> bool =>
213
isa TRIVIA.LINE_COMMENT(t) \/ isa TRIVIA.BLOCK_COMMENT(t);
214
215
emit_comment(t: TRIVIA) is
216
note(t.location);
217
218
if let bc: TRIVIA.BLOCK_COMMENT = t then
219
let lines = bc.text.split(['\n']);
220
let first mut = true;
221
for line in lines do
222
if !first then
223
_builder.hard_line();
224
fi
225
_builder.text(line);
226
first = false;
227
od
228
elif let lc: TRIVIA.LINE_COMMENT = t then
229
_builder.text(lc.text);
230
fi
231
si
232
233
// --- definition / statement lists: leading-trivia interleaving ---
234
235
visit(definitions: Definitions.LIST) is
236
for d in definitions do
237
flush_leading(d.location.start);
238
239
d.accept(self);
240
241
if let variable: Variables.VARIABLE = d then
242
if let variable.name? /\ name.name.starts_with("$") then
243
write_line(";");
244
fi
245
fi
246
od
247
si
248
249
visit(list: Statements.LIST) is
250
for s in list do
251
flush_leading(s.location.start);
252
s.accept(self);
253
od
254
si
255
256
visit(block: Bodies.BLOCK) is
257
write_line("is");
258
indent();
259
block.statements.accept(self);
260
flush_leading(block.location.end);
261
outdent();
262
write("si");
263
si
264
265
// --- visits the plain printer never implemented ---
266
267
visit(let_in: Expressions.LET_IN) is
268
note(let_in.location);
269
write("let ");
270
if let_in.want_dispose then
271
write("use ");
272
fi
273
let_in.variables.accept(self);
274
write(" in ");
275
let_in.expression.accept(self);
276
si
277
278
visit(assert_in: Expressions.ASSERT_IN) is
279
note(assert_in.location);
280
write("assert ");
281
assert_in.condition.accept(self);
282
if assert_in.message? then
283
write(" else ");
284
assert_in.message!.accept(self);
285
fi
286
write(" in ");
287
assert_in.expression.accept(self);
288
si
289
290
visit(recurse: Expressions.RECURSE) is
291
note(recurse.location);
292
write("rec");
293
si
294
295
// --- faithfulness fix: an `if ... fi` in expression position is
296
// Expressions.STATEMENT wrapping Statements.IF. The inherited
297
// visit renders the statement form — `;`-terminated branch
298
// bodies plus a trailing `;` — which is invalid in expression
299
// position. Render the expression form instead. ---
300
301
visit(statement: Expressions.STATEMENT) is
302
note(statement.location);
303
304
if let if_statement: Statements.IF = statement.statement then
305
emit_if_expression(if_statement);
306
elif let case_statement: Statements.CASE = statement.statement then
307
emit_case_expression(case_statement);
308
else
309
statement.statement.accept(self);
310
fi
311
si
312
313
// `val ... lav` block expression. The keyword pair is load-
314
// bearing; each statement renders normally (with its own `;`)
315
// and the surrounding context decides whether the block's value
316
// is consumed.
317
visit(block: Expressions.VAL_BLOCK) is
318
note(block.location);
319
320
write_line("val");
321
indent();
322
block.body.accept(self);
323
outdent();
324
write("lav");
325
si
326
327
// --- faithfulness fix: the inherited visit(Statements.LET) never
328
// emits the `use` disposal keyword, so `let use x = e` round-trips
329
// to `let x = e`, silently dropping the disposal. ---
330
331
visit(l: Statements.LET) is
332
write("let ");
333
334
if l.want_dispose then
335
write("use ");
336
fi
337
338
l.variables.accept(self);
339
write_line(";");
340
si
341
342
// --- faithfulness fix: the inherited visit(Statements.IF) ignores a
343
// branch binding, so `if let v = e then` renders as a bare `else`
344
// with the binding dropped. ---
345
346
visit(if_statement: Statements.IF) is
347
note(if_statement.location);
348
349
let first mut = true;
350
for branch in if_statement.branches do
351
if let branch.binding? then
352
if first then
353
write("if let ");
354
else
355
write("elif let ");
356
fi
357
binding.accept(self);
358
if let branch.condition? then
359
write(" /\\ ");
360
condition.accept(self);
361
fi
362
write_line(" then");
363
elif let branch.condition? then
364
if first then
365
write("if ");
366
else
367
write("elif ");
368
fi
369
condition.accept(self);
370
write_line(" then");
371
else
372
write_line("else");
373
fi
374
375
indent();
376
branch.body.accept(self);
377
outdent();
378
379
first = false;
380
od
381
382
write_line("fi");
383
si
384
385
// --- the REFUTABLE_BINDING node carries the `pattern[: T] =
386
// scrutinee[ /\ guard]` shape that drives an `if let` arm,
387
// or the leaf-name shorthand `path?` / `path: T` when the
388
// variable name was inferred from the path. The `if let ` /
389
// `elif let ` keyword is emitted by the surrounding
390
// visit(Statements.IF); this renders the clause payload
391
// only. ---
392
393
visit(rb: Statements.REFUTABLE_BINDING) is
394
note(rb.location);
395
396
let first mut = true;
397
398
for c in rb.clauses do
399
if !first then
400
write(", ");
401
fi
402
403
if c.is_inferred_name then
404
c.scrutinee.accept(self);
405
406
if let c.narrow_type_expression? then
407
write(": ");
408
narrow_type_expression.accept(self);
409
else
410
write("?");
411
fi
412
else
413
c.pattern.accept(self);
414
415
if let c.narrow_type_expression? then
416
write(": ");
417
narrow_type_expression.accept(self);
418
fi
419
420
write(" = ");
421
c.scrutinee.accept(self);
422
fi
423
424
if let c.guard? then
425
write(" /\\ ");
426
guard.accept(self);
427
fi
428
429
first = false;
430
od
431
si
432
433
// --- faithfulness fix: the inherited visit(Statements.FOR) emits
434
// `for` with a trailing newline, stranding it on its own line. ---
435
436
visit(for_statement: Statements.FOR) is
437
note(for_statement.location);
438
439
write("for ");
440
441
let variable = for_statement.variable;
442
if variable? then
443
variable.accept(self);
444
fi
445
446
write(" in ");
447
448
let expression = for_statement.expression;
449
if expression? then
450
expression.accept(self);
451
fi
452
453
write_line(" do");
454
455
indent();
456
457
let body = for_statement.body;
458
if body? then
459
body.accept(self);
460
fi
461
462
outdent();
463
464
write_line("od");
465
si
466
467
emit_if_expression(if_statement: Statements.IF) is
468
_builder.begin_group();
469
470
let first mut = true;
471
for branch in if_statement.branches do
472
if !first then
473
_builder.line();
474
fi
475
476
if let branch.binding? then
477
if first then
478
write("if let ");
479
else
480
write("elif let ");
481
fi
482
binding.accept(self);
483
if let branch.condition? then
484
write(" /\\ ");
485
condition.accept(self);
486
fi
487
write(" then");
488
elif let branch.condition? then
489
if first then
490
write("if ");
491
else
492
write("elif ");
493
fi
494
condition.accept(self);
495
write(" then");
496
else
497
write("else");
498
fi
499
500
_builder.begin_nest(4);
501
_builder.line();
502
emit_branch_value(branch.body);
503
_builder.end_nest();
504
505
first = false;
506
od
507
508
_builder.line();
509
write("fi");
510
_builder.end_group();
511
si
512
513
// Emit an if-expression branch body. The branch's value is its last
514
// statement; if that is an expression statement, emit the bare
515
// expression (no `;`). Any leading statements render normally.
516
emit_branch_value(body: Statements.LIST) is
517
let statements = LIST[Statements.Statement]();
518
for s in body do
519
statements.add(s);
520
od
521
522
let i mut = 0;
523
while i < statements.count do
524
let s = statements[i];
525
if i == statements.count - 1 /\ isa Statements.EXPRESSION(s) then
526
let expression_statement = cast Statements.EXPRESSION(s);
527
expression_statement.expression.accept(self);
528
else
529
s.accept(self);
530
fi
531
i = i + 1;
532
od
533
si
534
535
// --- faithfulness fix: string interpolation. The inherited visit
536
// runs each literal fragment through visit(STRING), which wraps
537
// it in quotes — producing invalid nested-string output. Emit the
538
// literal fragments as raw escaped text instead. ---
539
540
visit(interpolation: Expressions.STRING_INTERPOLATION) is
541
note(interpolation.location);
542
543
write(cast char(34));
544
545
for fragment in interpolation.values do
546
if fragment.is_expression then
547
write('{');
548
fragment.expression.accept(self);
549
if let fragment.alignment? then
550
write(',');
551
alignment.accept(self);
552
fi
553
if fragment.format? then
554
let format = fragment.format!;
555
write(':');
556
write(format);
557
fi
558
write('}');
559
else
560
emit_interpolation_literal(fragment);
561
fi
562
od
563
564
write(cast char(34));
565
si
566
567
emit_interpolation_literal(fragment: Expressions.INTERPOLATION_FRAGMENT) is
568
let literal = cast Expressions.Literals.STRING?(fragment.expression);
569
570
if !literal? then
571
return;
572
fi
573
574
for c in literal.value_string do
575
if c == '{' then
576
write('{');
577
write('{');
578
elif c == '}' then
579
write('}');
580
write('}');
581
else
582
write_escape_char(c);
583
fi
584
od
585
si
586
587
// --- faithfulness fix: the inherited write_escape_char renders
588
// control characters via string.format("X", ci), which returns
589
// the literal "X" (no placeholder), mangling e.g. '\n' to '\X'.
590
// Emit the escapes the tokenizer actually understands. ---
591
592
write_escape_char(c: char) is
593
let ci = cast int(c);
594
595
if c == '\n' then
596
write("\\");
597
write('n');
598
elif ci == 9 then
599
write("\\");
600
write('t');
601
elif ci == 13 then
602
write("\\");
603
write('r');
604
elif ci == 34 then
605
write("\\");
606
write(cast char(34));
607
elif ci == 92 then
608
write("\\\\");
609
elif ci < 32 then
610
write("\\");
611
write("{(ci >> 6) & 7}");
612
write("{(ci >> 3) & 7}");
613
write("{ci & 7}");
614
else
615
write(c);
616
fi
617
si
618
619
// --- faithfulness fix: a lambda's argument list must be parenthesised
620
// unless it is a single argument with an inferred return type —
621
// the only paren-free lambda form. A bare empty list (`=> body`)
622
// or a bare multi-argument list does not reparse as a lambda, and
623
// an explicit return type needs the parens or `arg: T -> U`
624
// reparses as an argument of function type `T -> U`. ---
625
626
visit(function: Expressions.FUNCTION) is
627
note(function.location);
628
629
let explicit_return = !isa TypeExpressions.INFER(function.type_expression);
630
631
let parenthesise = explicit_return \/ function.arguments.count != 1;
632
633
if parenthesise then
634
write("(");
635
fi
636
637
let first mut = true;
638
for a in function.arguments do
639
if !first then
640
write(", ");
641
fi
642
a.accept(self);
643
first = false;
644
od
645
646
if parenthesise then
647
write(")");
648
fi
649
650
if explicit_return then
651
write(" -> ");
652
function.type_expression.accept(self);
653
fi
654
655
write(" ");
656
function.body.accept(self);
657
si
658
659
// --- faithfulness fix: an unresolved AMBIGUOUS_EXPRESSION (a parse-
660
// time `x[y]` that could be a generic application or an index)
661
// has the same surface syntax either way. The plain printer emits
662
// a debug `(ambiguous ... or ...)` form, which is not valid ghūl. ---
663
664
visit(ambiguous: Expressions.AMBIGUOUS_EXPRESSION) is
665
note(ambiguous.location);
666
667
if let ambiguous.left? then
668
left.accept(self);
669
write(".");
670
fi
671
672
ambiguous.identifier.accept(self);
673
write("[");
674
ambiguous.type_arguments.accept(self);
675
write("]");
676
si
677
678
// --- faithfulness fix: STRUCT printed as `struct`, not `trait`;
679
// CLASS / STRUCT carry an optional primary-constructor header
680
// `(params)` between the type-argument list and the ancestor
681
// list, and a body-less primary-ctor form `class X(p: T);` is
682
// emitted as the `;` shorthand rather than `is si`. ---
683
684
visit(`class: Definitions.CLASS) is
685
emit_classy("class", `class);
686
si
687
688
visit(`struct: Definitions.STRUCT) is
689
emit_classy("struct", `struct);
690
si
691
692
// `partial Target[args] is … si` — no ancestor clause, so emit_classy
693
// (which only writes `: …` when ancestors are present) prints it faithfully.
694
visit(`partial: Definitions.PARTIAL) is
695
emit_classy("partial", `partial);
696
si
697
698
// `impl Interface for Target[args] is … si` — the interface leads and the
699
// target follows `for`, the reverse of the `Target: Interface` shape
700
// emit_classy assumes, so it needs its own emitter.
701
visit(`impl: Definitions.IMPL) is
702
note(`impl.location);
703
704
write("impl ");
705
706
if `impl.ancestors? then
707
`impl.ancestors.accept(self);
708
fi
709
710
write(" for ");
711
`impl.name.accept(self);
712
713
if `impl.arguments? then
714
write("[");
715
`impl.arguments!.accept(self);
716
write("]");
717
fi
718
719
if !`impl.modifiers.is_empty then
720
write(" ");
721
emit_modifiers(`impl.modifiers);
722
fi
723
724
write_line(" is");
725
indent();
726
`impl.body.accept(self);
727
flush_leading(`impl.body.location.end);
728
outdent();
729
write_line("si");
730
si
731
732
emit_classy(keyword: string, classy: Definitions.Classy) is
733
note(classy.location);
734
735
write(keyword);
736
write(" ");
737
classy.name.accept(self);
738
739
if classy.arguments? then
740
write("[");
741
classy.arguments!.accept(self);
742
write("]");
743
fi
744
745
if classy.primary_params? then
746
let primary_params = classy.primary_params;
747
write("(");
748
emit_parameter_list(primary_params);
749
write(")");
750
fi
751
752
if classy.ancestors? then
753
write(": ");
754
classy.ancestors!.accept(self);
755
fi
756
757
if !classy.modifiers.is_empty then
758
write(" ");
759
emit_modifiers(classy.modifiers);
760
fi
761
762
if classy.primary_params? /\ _body_is_empty(classy.body) then
763
write_line(";");
764
return;
765
fi
766
767
write_line(" is");
768
indent();
769
classy.body.accept(self);
770
flush_leading(classy.body.location.end);
771
outdent();
772
write_line("si");
773
si
774
775
_body_is_empty(body: Definitions.LIST) -> bool is
776
for d in body do
777
return false;
778
od
779
return true;
780
si
781
782
// --- faithfulness fix: SUPER_CALL is a body declaration only
783
// reachable inside a primary-constructor class/struct body.
784
// The base printer's StrictVisitor base throws on unhandled
785
// node types — provide an explicit visit. ---
786
787
visit(super_call: Definitions.SUPER_CALL) is
788
note(super_call.location);
789
write("super(");
790
let first mut = true;
791
for a in super_call.args do
792
if !first then
793
write(", ");
794
fi
795
a.accept(self);
796
first = false;
797
od
798
write_line(");");
799
si
800
801
// --- faithfulness fix: the base printer's UNION visit ignores
802
// the primary-constructor header (the parens after the union
803
// name) and so drops it on round-trip. Emit it. ---
804
805
visit(`union: Definitions.UNION) is
806
note(`union.location);
807
808
write("union ");
809
`union.name.accept(self);
810
811
if `union.arguments? then
812
write("[");
813
`union.arguments!.accept(self);
814
write("]");
815
fi
816
817
if `union.primary_params? then
818
let primary_params = `union.primary_params;
819
write("(");
820
emit_parameter_list(primary_params);
821
write(")");
822
fi
823
824
if `union.ancestors? /\ `union.ancestors.count > 0 then
825
write(": ");
826
`union.ancestors!.accept(self);
827
fi
828
829
if !`union.modifiers.is_empty then
830
write(" ");
831
emit_modifiers(`union.modifiers);
832
fi
833
834
write_line(" is");
835
indent();
836
837
let previous_has_primary = _union_has_primary_params;
838
_union_has_primary_params = `union.primary_params?;
839
840
try
841
`union.body.accept(self);
842
finally
843
_union_has_primary_params = previous_has_primary;
844
yrt
845
846
flush_leading(`union.body.location.end);
847
outdent();
848
write_line("si");
849
si
850
851
// --- faithfulness fix: a typed-union VARIANT is parsed with a
852
// synthetic `public field` modifier list and an always-empty
853
// body block, so the inherited visit prints
854
// `RED public field is\nsi`. A variant has no user-written
855
// body and the modifiers are not source-visible — emit the
856
// `name(fields)? default? ;` form the user actually wrote. ---
857
858
visit(variant: Definitions.VARIANT) is
859
note(variant.location);
860
861
variant.name.accept(self);
862
863
if variant.fields.count > 0 /\ !_variant_fields_are_implicit_splice(variant) then
864
write("(");
865
variant.fields.accept(self);
866
write(")");
867
fi
868
869
if variant.is_default then
870
write(" default");
871
fi
872
873
write_line(";");
874
si
875
876
// True when the variant's only field is a single `..` splice and
877
// we are inside a primary-header union — the splice is then
878
// implied by the bare `NAME;` form and the parens add noise.
879
_variant_fields_are_implicit_splice(variant: Definitions.VARIANT) -> bool is
880
if !_union_has_primary_params then
881
return false;
882
fi
883
884
if variant.fields.count != 1 then
885
return false;
886
fi
887
888
for v in variant.fields do
889
return v.is_splice;
890
od
891
892
return false;
893
si
894
895
// --- faithfulness fix: `case` statement / expression. The base
896
// printer emits a legacy `when X :` / `default` / `esac`
897
// shape; the parser accepts only `when X then` / `else` /
898
// `esac`. Binding-pattern arms (`when v: int then`) and
899
// literal-leaf patterns live on the arm's `pattern` field
900
// rather than its `expressions` list and must be emitted
901
// from there. ---
902
903
visit(`case: Statements.CASE) is
904
emit_case_header(`case);
905
906
for m in `case.matches do
907
m.accept(self);
908
od
909
910
write_line("esac");
911
si
912
913
visit(match: Statements.CASE_MATCH) is
914
emit_case_match(match, false);
915
si
916
917
emit_case_header(case_statement: Statements.CASE) is
918
note(case_statement.location);
919
write("case ");
920
case_statement.expression.accept(self);
921
write_line();
922
si
923
924
emit_case_match(match: Statements.CASE_MATCH, as_expression: bool) is
925
note(match.location);
926
927
if let match.pattern? then
928
write("when ");
929
pattern.accept(self);
930
931
if let match.guard? then
932
write(" /\\ ");
933
guard.accept(self);
934
fi
935
936
write_line(" then");
937
elif match.expressions? then
938
write("when ");
939
let first mut = true;
940
for e in match.expressions! do
941
if !first then
942
write(", ");
943
fi
944
e.accept(self);
945
first = false;
946
od
947
write_line(" then");
948
else
949
write_line("else");
950
fi
951
952
indent();
953
954
if as_expression then
955
emit_branch_value(match.statements);
956
write_line();
957
else
958
match.statements.accept(self);
959
fi
960
961
outdent();
962
si
963
964
emit_case_expression(case_statement: Statements.CASE) is
965
emit_case_header(case_statement);
966
967
for m in case_statement.matches do
968
emit_case_match(m, true);
969
od
970
971
write("esac");
972
si
973
974
// --- faithfulness fix: a VARIABLE carries three optional pieces
975
// the inherited printer never emits — the `..` splice marker
976
// (only inside a secondary `init` formal-arg list), the
977
// trailing `mut` keyword (set by the parser when source had
978
// `mut`), and a trailing modifier suffix list (only on
979
// primary-ctor parameter declarations, where `public`,
980
// `field`, and `init` describe the auto-generated body
981
// member). ---
982
983
visit(variable: Variables.VARIABLE) is
984
note(variable.location);
985
986
if variable.is_splice then
987
write("..");
988
return;
989
fi
990
991
variable.left.accept(self);
992
993
if !isa TypeExpressions.INFER(variable.type_expression) then
994
write(": ");
995
variable.type_expression.accept(self);
996
fi
997
998
if variable.is_mutable_marked then
999
write(" mut");
1000
fi
1001
1002
if let variable.modifiers? /\ !modifiers.is_empty then
1003
write(" ");
1004
emit_modifiers(modifiers);
1005
fi
1006
1007
let initializer = variable.initializer;
1008
if initializer? then
1009
write(" = ");
1010
initializer.accept(self);
1011
fi
1012
si
1013
1014
// --- faithfulness fix: a body-less property (a field) is terminated
1015
// with `;` and a newline, which the plain printer omits ---
1016
1017
visit(property: Definitions.PROPERTY) is
1018
if property.name? then
1019
property.name.accept(self);
1020
fi
1021
1022
if !isa TypeExpressions.INFER(property.type_expression) then
1023
write(": ");
1024
property.type_expression.accept(self);
1025
fi
1026
1027
if !property.modifiers.is_empty then
1028
write(" ");
1029
emit_modifiers(property.modifiers);
1030
fi
1031
1032
if !property.read_body? /\ !property.assign_body? then
1033
write_line(";");
1034
return;
1035
fi
1036
1037
let out_again = indent_property(property.read_body?, property.assign_body?);
1038
1039
if property.read_body? then
1040
property.read_body.accept(self);
1041
if property.assign_body? then
1042
write_line(",");
1043
else
1044
after_body(property.read_body!);
1045
fi
1046
else
1047
write(" ");
1048
fi
1049
1050
if property.assign_body? then
1051
write("= ");
1052
property.assign_argument!.accept(self);
1053
property.assign_body!.accept(self);
1054
after_body(property.assign_body!);
1055
fi
1056
1057
if out_again then
1058
outdent();
1059
fi
1060
si
1061
1062
// Emit modifiers without the trailing space the inherited
1063
// Modifiers.LIST visit appends (which would strand a space before a
1064
// following `;`).
1065
emit_modifiers(modifiers: Modifiers.LIST) is
1066
let first mut = true;
1067
1068
if let modifiers.access_modifier? then
1069
access_modifier.accept(self);
1070
first = false;
1071
fi
1072
1073
if let modifiers.storage_class? then
1074
if !first then
1075
write(" ");
1076
fi
1077
storage_class.accept(self);
1078
first = false;
1079
fi
1080
1081
if modifiers.is_pure then
1082
if !first then
1083
write(" ");
1084
fi
1085
write("pure");
1086
first = false;
1087
fi
1088
si
1089
1090
// --- wrapping: call argument lists and function parameter lists ---
1091
1092
visit(call: Expressions.CALL) is
1093
note(call.location);
1094
if call.is_pipe_wrap then
1095
for argument in call.arguments do
1096
argument.accept(self);
1097
od
1098
// Trailing space: `.` is an operator char, so `|.` would
1099
// tokenise as a single operator instead of pipe-then-dot.
1100
write(" | ");
1101
return;
1102
fi
1103
if call.is_thread_first /\ call.arguments.count >= 1 then
1104
call.arguments.expressions[0].accept(self);
1105
write(" |> ");
1106
call.function.accept(self);
1107
write("(");
1108
emit_argument_list(call.arguments, 1);
1109
write(")");
1110
else
1111
call.function.accept(self);
1112
write("(");
1113
emit_argument_list(call.arguments);
1114
write(")");
1115
fi
1116
si
1117
1118
visit(function: Definitions.FUNCTION) is
1119
if let function.name? then
1120
name.accept(self);
1121
fi
1122
1123
if _has_type_arguments(function.generic_arguments) then
1124
write("[");
1125
function.generic_arguments.accept(self);
1126
write("]");
1127
fi
1128
1129
write("(");
1130
emit_parameter_list(function.arguments);
1131
write(")");
1132
1133
if !isa TypeExpressions.INFER(function.type_expression) then
1134
write(" -> ");
1135
function.type_expression.accept(self);
1136
fi
1137
1138
if !function.modifiers.is_empty then
1139
write(" ");
1140
emit_modifiers(function.modifiers);
1141
fi
1142
1143
let body = function.body;
1144
1145
if body? then
1146
write(" ");
1147
body.accept(self);
1148
fi
1149
1150
after_body(body);
1151
si
1152
1153
emit_parameter_list(parameters: Variables.LIST) is
1154
if !_has_parameters(parameters) then
1155
return;
1156
fi
1157
1158
_builder.begin_group();
1159
_builder.begin_nest(4);
1160
_builder.soft_line();
1161
1162
let first mut = true;
1163
for p in parameters do
1164
if !first then
1165
_builder.text(",");
1166
_builder.line();
1167
fi
1168
p.accept(self);
1169
first = false;
1170
od
1171
1172
_builder.end_nest();
1173
_builder.soft_line();
1174
_builder.end_group();
1175
si
1176
1177
_has_parameters(parameters: Variables.LIST?) -> bool is
1178
if !parameters? then
1179
return false;
1180
fi
1181
for p in parameters do
1182
return true;
1183
od
1184
return false;
1185
si
1186
1187
_has_type_arguments(arguments: TypeExpressions.LIST?) -> bool is
1188
if !arguments? then
1189
return false;
1190
fi
1191
for a in arguments do
1192
return true;
1193
od
1194
return false;
1195
si
1196
1197
// --- faithfulness fix: a NAMED_TUPLE_ELEMENT in type-parameter
1198
// position can carry up to four pieces of constraint syntax
1199
// past its name:type slot — the trailing kind keyword after a
1200
// type bound (`[T: A class]`), a `new` ctor constraint, and a
1201
// trailing `in` / `out` variance. The inherited visit emits
1202
// only `name: type_expression`, dropping the other three.
1203
// The `[T: out]` variance-only form also needs its
1204
// TYPE_PARAMETER_CONSTRAINT(NONE) placeholder suppressed
1205
// (the inherited path renders it as `?`). ---
1206
1207
visit(element: TypeExpressions.NAMED_TUPLE_ELEMENT) is
1208
note(element.location);
1209
element.name.accept(self);
1210
write(": ");
1211
1212
let constraint = cast TypeExpressions.TYPE_PARAMETER_CONSTRAINT?(element.type_expression);
1213
let is_placeholder =
1214
constraint? /\
1215
constraint.kind == Semantic.Symbols.TypeParameterConstraintKind.NONE;
1216
let need_separator mut = false;
1217
1218
if !is_placeholder then
1219
element.type_expression.accept(self);
1220
need_separator = true;
1221
fi
1222
1223
if element.combined_kind != Semantic.Symbols.TypeParameterConstraintKind.NONE then
1224
if need_separator then
1225
write(" ");
1226
fi
1227
write(_constraint_kind_keyword(element.combined_kind));
1228
need_separator = true;
1229
fi
1230
1231
if element.has_constructor then
1232
if need_separator then
1233
write(" ");
1234
fi
1235
write("new");
1236
need_separator = true;
1237
fi
1238
1239
if element.variance == Semantic.Types.TypeVariance.COVARIANT then
1240
if need_separator then
1241
write(" ");
1242
fi
1243
write("out");
1244
elif element.variance == Semantic.Types.TypeVariance.CONTRAVARIANT then
1245
if need_separator then
1246
write(" ");
1247
fi
1248
write("in");
1249
fi
1250
si
1251
1252
_constraint_kind_keyword(kind: Semantic.Symbols.TypeParameterConstraintKind) -> string is
1253
if kind == Semantic.Symbols.TypeParameterConstraintKind.REFERENCE then
1254
return "class";
1255
elif kind == Semantic.Symbols.TypeParameterConstraintKind.VALUE then
1256
return "struct";
1257
elif kind == Semantic.Symbols.TypeParameterConstraintKind.OPTIONAL then
1258
return "optional";
1259
fi
1260
return "";
1261
si
1262
1263
emit_argument_list(arguments: Expressions.LIST) is
1264
emit_argument_list(arguments, 0);
1265
si
1266
1267
emit_argument_list(arguments: Expressions.LIST, start: int) is
1268
if arguments.count <= start then
1269
return;
1270
fi
1271
1272
_builder.begin_group();
1273
_builder.begin_nest(4);
1274
_builder.soft_line();
1275
1276
let first mut = true;
1277
for i in start..arguments.count do
1278
if !first then
1279
_builder.text(",");
1280
_builder.line();
1281
fi
1282
arguments.expressions[i].accept(self);
1283
first = false;
1284
od
1285
1286
// Written hard against the argument it trails, inside the nest,
1287
// so a broken layout puts it at the end of that argument's line
1288
// and leaves the closing bracket on its own.
1289
if arguments.has_trailing_comma then
1290
_builder.text(",");
1291
fi
1292
1293
_builder.end_nest();
1294
_builder.soft_line();
1295
_builder.end_group();
1296
si
1297
1298
_has_arguments(arguments: Expressions.LIST) -> bool is
1299
for a in arguments do
1300
return true;
1301
od
1302
return false;
1303
si
1304
si
1305
si