Skip to content
← Back

src/syntax/process/printer/formatter_statements.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
// Statement and case formatting: lists, blocks, lets, if statements and expressions,
11
// refutable bindings, for loops and case arms.
12
partial FORMATTER is
13
14
// --- definition / statement lists: leading-trivia interleaving ---
15
16
// A file with no namespace can carry bare statements at its root
17
// (synthesise-top-level-entry consumes them into a global entry
18
// point before declare-symbols), interleaved with global
19
// definitions in source order - GHUL.md: "may be interleaved with
20
// global definitions, which are visible regardless of where they
21
// appear." `definitions` and `top_level_statements` are two
22
// separately-ordered runs, so a formatter that rendered every
23
// definition before every statement would silently reorder the
24
// file. Walk both by source position instead.
25
visit(definitions: Definitions.LIST) is
26
let items = LIST[Trees.Node]()
27
28
for d in definitions do
29
items.add(d)
30
od
31
32
if let statements = definitions.top_level_statements then
33
for s in statements do
34
items.add(s)
35
od
36
fi
37
38
items.sort((a, b) => a.location.start - b.location.start)
39
40
let first mut = true
41
42
for item in items do
43
flush_leading(item.location.start)
44
45
// Nothing has been written yet before the very first item
46
// (the root Definitions.LIST is visited before anything
47
// else), so `at_line_start` is still at its unset default
48
// there - only break between items, never before the first.
49
if !first /\ !at_line_start then
50
write_line()
51
fi
52
53
item.accept(self)
54
55
first = false
56
57
if let variable: Variables.VARIABLE = item then
58
if let variable.name? /\ name.name.starts_with("$") then
59
write_terminator()
60
fi
61
fi
62
od
63
si
64
65
visit(list: Statements.LIST) is
66
for s in list do
67
// A statement ending in a block terminator leaves the line open,
68
// and running the next statement onto it produces source that no
69
// longer parses - `odwrite_line(...)`.
70
if !at_line_start then
71
write_line()
72
fi
73
74
flush_leading(s.location.start)
75
s.accept(self)
76
od
77
si
78
79
visit(block: Bodies.BLOCK) is
80
// An empty body renders on the header's line - `init() is si` -
81
// rather than splitting over two. A body carrying comments keeps
82
// the general path so they stay inside the block.
83
if block.statements.is_empty /\ ! _has_trivia_before(block.location.end) then
84
write("is si")
85
return
86
fi
87
88
write_line("is")
89
indent()
90
91
// A final statement that reached the body terminator without
92
// its `;` is an implicit tail return: it renders as a bare
93
// expression, since printing the `;` would turn the return
94
// back into a discarded statement.
95
let tail_expression: Trees.Expressions.Expression? mut = null
96
97
if !block.statements.last_was_terminated then
98
if let l = block.statements.last then
99
if let te = cast Statements.EXPRESSION?(l) then
100
tail_expression = te.expression
101
fi
102
fi
103
fi
104
105
if let te = tail_expression then
106
let items = LIST[Statements.Statement]()
107
108
for s in block.statements do
109
items.add(s)
110
od
111
112
let i mut = 0
113
while i < items.count - 1 do
114
// A statement ending in a block terminator leaves the
115
// line open, and the next statement's leading comment
116
// would be written onto it. So the break comes before
117
// the flush, as it does in visit(Statements.LIST).
118
if ! at_line_start then
119
write_line()
120
fi
121
122
flush_leading(items[i].location.start)
123
items[i].accept(self)
124
i = i + 1
125
od
126
127
if ! at_line_start then
128
write_line()
129
fi
130
131
flush_leading(items[items.count - 1].location.start)
132
te.accept(self)
133
write_line("")
134
else
135
block.statements.accept(self)
136
fi
137
138
// The last statement may have ended in a block terminator of its
139
// own, leaving the line open: `si` must not run onto it.
140
if !at_line_start then
141
write_line()
142
fi
143
144
flush_leading(block.location.end)
145
outdent()
146
write("si")
147
si
148
149
// --- visits the plain printer never implemented ---
150
151
visit(let_in: Expressions.LET_IN) is
152
note(let_in.location)
153
write("let ")
154
if let_in.want_dispose then
155
write("use ")
156
fi
157
let_in.variables.accept(self)
158
write(" in ")
159
let_in.expression.accept(self)
160
si
161
162
visit(assert_in: Expressions.ASSERT_IN) is
163
note(assert_in.location)
164
write("assert ")
165
assert_in.condition.accept(self)
166
if assert_in.message? then
167
write(" else ")
168
assert_in.message!.accept(self)
169
fi
170
write(" in ")
171
assert_in.expression.accept(self)
172
si
173
174
visit(recurse: Expressions.RECURSE) is
175
note(recurse.location)
176
write("rec")
177
si
178
179
// --- faithfulness fix: an `if ... fi` in expression position is
180
// Expressions.STATEMENT wrapping Statements.IF. The inherited
181
// visit renders the statement form — `;`-terminated branch
182
// bodies plus a trailing `;` — which is invalid in expression
183
// position. Render the expression form instead. ---
184
185
visit(statement: Expressions.STATEMENT) is
186
note(statement.location)
187
188
if let if_statement: Statements.IF = statement.statement then
189
emit_if_expression(if_statement)
190
elif let case_statement: Statements.CASE = statement.statement then
191
emit_case_expression(case_statement)
192
elif let for_statement: Statements.FOR = statement.statement then
193
emit_for_expression(for_statement)
194
elif let do_statement: Statements.DO = statement.statement then
195
emit_do_expression(do_statement)
196
elif let throw_statement: Statements.THROW = statement.statement then
197
// `=> throw E` stubs a diverging body. The statement form
198
// writes its own `;`, which the member's `after_body` would
199
// duplicate - render the expression form instead.
200
write("throw")
201
if throw_statement.expression? then
202
write(" ")
203
throw_statement.expression.accept(self)
204
fi
205
else
206
statement.statement.accept(self)
207
fi
208
si
209
210
// `val ... lav` block expression. The keyword pair is load-
211
// bearing; each statement renders normally (with its own `;`)
212
// and the surrounding context decides whether the block's value
213
// is consumed.
214
visit(block: Expressions.VAL_BLOCK) is
215
note(block.location)
216
217
if block.is_parenthesised then
218
write_line("(")
219
indent()
220
block.body.accept(self)
221
outdent()
222
write(")")
223
else
224
write_line("val")
225
indent()
226
block.body.accept(self)
227
outdent()
228
write("lav")
229
fi
230
si
231
232
// --- faithfulness fix: the inherited visit(Statements.LET) never
233
// emits the `use` disposal keyword, so `let use x = e` round-trips
234
// to `let x = e`, silently dropping the disposal. ---
235
236
visit(l: Statements.LET) is
237
write("let ")
238
239
if l.want_dispose then
240
write("use ")
241
fi
242
243
_in_let = true
244
l.variables.accept(self)
245
_in_let = false
246
write_terminator()
247
si
248
249
// --- faithfulness fix: the inherited visit(Statements.IF) ignores a
250
// branch binding, so `if let v = e then` renders as a bare `else`
251
// with the binding dropped. ---
252
253
visit(if_statement: Statements.IF) is
254
note(if_statement.location)
255
256
let branches = LIST[Statements.IF_BRANCH]()
257
for b in if_statement.branches do
258
branches.add(b)
259
od
260
261
let first mut = true
262
let i mut = 0
263
for branch in branches do
264
// The previous branch ended with a statement that may
265
// itself have closed a block, leaving the line open:
266
// `elif` and `else` must not run onto it.
267
if !first /\ !at_line_start then
268
write_line()
269
fi
270
271
if let branch.binding? then
272
if first then
273
write("if let ")
274
else
275
write("elif let ")
276
fi
277
binding.accept(self)
278
if let branch.condition? then
279
write(" /\\ ")
280
condition.accept(self)
281
fi
282
write_line(" then")
283
elif let branch.condition? then
284
if first then
285
write("if ")
286
else
287
write("elif ")
288
fi
289
condition.accept(self)
290
write_line(" then")
291
else
292
write_line("else")
293
fi
294
295
indent()
296
branch.body.accept(self)
297
298
// A comment after the branch's last statement belongs inside
299
// the branch: consume it while still indented, or it is
300
// emitted after the `fi` - or, ahead of the next clause's
301
// keyword, trails that keyword's line instead.
302
flush_leading(
303
if i + 1 < branches.count then
304
branches[i + 1].location.start
305
else
306
if_statement.location.end
307
fi)
308
309
outdent()
310
311
first = false
312
i = i + 1
313
od
314
315
// The last branch's body may have ended in a block terminator
316
// of its own, leaving the line open: `fi` must not run onto it.
317
if !at_line_start then
318
write_line()
319
fi
320
321
write_line("fi")
322
si
323
324
// --- the REFUTABLE_BINDING node carries the `pattern[: T] =
325
// scrutinee[ /\ guard]` shape that drives an `if let` arm,
326
// or the leaf-name shorthand `path?` / `path: T` when the
327
// variable name was inferred from the path. The `if let ` /
328
// `elif let ` keyword is emitted by the surrounding
329
// visit(Statements.IF); this renders the clause payload
330
// only. ---
331
332
visit(rb: Statements.REFUTABLE_BINDING) is
333
note(rb.location)
334
335
let first mut = true
336
337
for c in rb.clauses do
338
if !first then
339
write(", ")
340
fi
341
342
if c.is_inferred_name then
343
c.scrutinee.accept(self)
344
345
if let c.narrow_type_expression? then
346
write(": ")
347
narrow_type_expression.accept(self)
348
else
349
write("?")
350
fi
351
else
352
c.pattern.accept(self)
353
354
if let c.narrow_type_expression? then
355
write(": ")
356
narrow_type_expression.accept(self)
357
fi
358
359
write(" = ")
360
c.scrutinee.accept(self)
361
fi
362
363
if let c.guard? then
364
write(" /\\ ")
365
guard.accept(self)
366
fi
367
368
first = false
369
od
370
si
371
372
// --- faithfulness fix: the inherited visit(Statements.FOR) emits
373
// `for` with a trailing newline, stranding it on its own line. ---
374
375
visit(for_statement: Statements.FOR) is
376
note(for_statement.location)
377
378
write("for ")
379
380
let variable = for_statement.variable
381
if variable? then
382
variable.accept(self)
383
fi
384
385
write(" in ")
386
387
let expression = for_statement.expression
388
if expression? then
389
expression.accept(self)
390
fi
391
392
write_line(" do")
393
394
indent()
395
396
let body = for_statement.body
397
if body? then
398
body.accept(self)
399
fi
400
401
// A comment after the body's last statement belongs inside the
402
// loop: consume it while still indented, or it is emitted after
403
// the `od`.
404
flush_leading(for_statement.location.end)
405
406
outdent()
407
408
// Nested blocks each end with their own terminator, and the inner
409
// one leaves the line open: `od` must not run onto it.
410
if !at_line_start then
411
write_line()
412
fi
413
414
write("od")
415
si
416
417
// The base visit renders the statement form but never consumes the
418
// trivia queue, so a comment trailing the body is emitted after the
419
// `od` rather than inside the loop.
420
visit(`do: Statements.DO) is
421
note(`do.location)
422
423
if let `do.binding? then
424
write("while let ")
425
binding.accept(self)
426
write(" ")
427
elif let `do.condition? then
428
write("while ")
429
condition.accept(self)
430
write(" ")
431
fi
432
433
write_line("do")
434
indent()
435
`do.body.accept(self)
436
437
// A comment after the body's last statement belongs inside the
438
// loop: consume it while still indented, or it is emitted after
439
// the `od`.
440
flush_leading(`do.location.end)
441
442
outdent()
443
444
// Nested blocks each end with their own terminator, and the inner
445
// one leaves the line open: `od` must not run onto it.
446
if !at_line_start then
447
write_line()
448
fi
449
450
write_line("od")
451
si
452
453
// The base visit renders the statement form but never consumes the
454
// trivia queue, so a comment trailing a body is emitted after the
455
// `yrt` - or, ahead of a `catch` or `finally` keyword, trails that
456
// keyword's line - rather than staying in the body it was written in.
457
visit(`try: Statements.TRY) is
458
note(`try.location)
459
460
write_line("try")
461
indent()
462
`try.body.accept(self)
463
464
let catches = LIST[Statements.CATCH]()
465
for c in `try.catches do
466
catches.add(c)
467
od
468
469
flush_leading(next_clause_start(`try, catches, 0))
470
471
outdent()
472
473
let i mut = 0
474
for c in catches do
475
// The body may have ended in a nested block, whose own
476
// terminator leaves the line open: `catch` must not run
477
// onto it, as the `yrt` below does not.
478
if !at_line_start then
479
write_line()
480
fi
481
482
write("catch ")
483
484
if let c.variable? then
485
variable.accept(self)
486
fi
487
write_line()
488
489
indent()
490
c.body.accept(self)
491
492
flush_leading(next_clause_start(`try, catches, i + 1))
493
494
outdent()
495
496
i = i + 1
497
od
498
499
let `finally = `try.`finally
500
501
if `finally? then
502
if !at_line_start then
503
write_line()
504
fi
505
506
write_line("finally")
507
indent()
508
`finally.accept(self)
509
510
// A comment after the last statement of the finally body
511
// belongs inside it: consume it while still indented, or it
512
// is emitted after the `yrt`.
513
flush_leading(`try.location.end)
514
515
outdent()
516
fi
517
518
if !at_line_start then
519
write_line()
520
fi
521
522
write_line("yrt")
523
si
524
525
// The position a body's trailing trivia is flushed against: the next
526
// `catch` clause when one follows, the `finally` body if the next
527
// thing is a finally, and the end of the try otherwise.
528
next_clause_start(`try: Statements.TRY, catches: LIST[Statements.CATCH], next: int) -> int is
529
if next < catches.count then
530
return catches[next].location.start
531
fi
532
533
let `finally = `try.`finally
534
535
if `finally? then
536
return `finally.location.start
537
fi
538
539
return `try.location.end
540
si
541
542
emit_if_expression(if_statement: Statements.IF) is
543
_builder.begin_group()
544
545
let first mut = true
546
for branch in if_statement.branches do
547
if !first /\ !at_line_start then
548
_builder.line()
549
fi
550
551
// Trivia (a comment, a blank line) between one branch's
552
// last statement and the next branch's keyword has to be
553
// consumed here - otherwise it stays in the trivia queue
554
// and surfaces later, wherever the next flush_leading
555
// happens to land.
556
if !first then
557
flush_leading(branch.location.start)
558
fi
559
560
if let branch.binding? then
561
if first then
562
write("if let ")
563
else
564
write("elif let ")
565
fi
566
binding.accept(self)
567
if let branch.condition? then
568
write(" /\\ ")
569
condition.accept(self)
570
fi
571
write(" then")
572
elif let branch.condition? then
573
if first then
574
write("if ")
575
else
576
write("elif ")
577
fi
578
condition.accept(self)
579
write(" then")
580
else
581
write("else")
582
fi
583
584
_builder.begin_nest(4)
585
_builder.line()
586
emit_branch_value(branch.body)
587
588
// A statement arm - a `return`, say - writes its own line
589
// break. The soft break that separates it from the next
590
// keyword would then be a second one, and the blank line
591
// it leaves comes back as another on every reformat.
592
_builder.end_nest()
593
594
first = false
595
od
596
597
flush_leading(if_statement.location.end)
598
599
if !at_line_start then
600
_builder.line()
601
fi
602
603
write("fi")
604
_builder.end_group()
605
si
606
607
// Emit an if-expression branch body. The branch's value is its last
608
// bare expression (no `;`); any leading statements render
609
// normally.
610
emit_branch_value(body: Statements.LIST) is
611
let statements = LIST[Statements.Statement]()
612
for s in body do
613
statements.add(s)
614
od
615
616
let i mut = 0
617
while i < statements.count do
618
let s = statements[i]
619
let is_last = i == statements.count - 1
620
621
// Leading trivia (blank lines, comments) between arm-body
622
// statements has to be consumed here, in place - otherwise
623
// it stays in the trivia queue and surfaces later, at
624
// whichever definition or statement next calls
625
// flush_leading, stacking with whatever blank line already
626
// belongs there.
627
// A statement ending in a block terminator - an arm body's
628
// `od` - leaves the line open, and the next statement's
629
// leading comment would be written onto it, so the break
630
// comes first. The arm's own opening break is a soft one,
631
// which `at_line_start` does not see, so this asks only
632
// about a break the previous statement should have left.
633
if i > 0 /\ !at_line_start then
634
write_line()
635
fi
636
637
flush_leading(s.location.start)
638
639
// A tail statement can itself be an if/case/for/do used as
640
// a value (implicit tail return). Each of those has its own
641
// no-trailing-terminator expression renderer, matching the
642
// dispatch in visit(Expressions.STATEMENT); the ordinary
643
// statement-form renderers (`visit(Statements.IF)` and
644
// friends) are for a statement in statement position, and
645
// terminate their own line.
646
if is_last /\ isa Statements.EXPRESSION(s) then
647
let expression_statement = cast Statements.EXPRESSION(s)
648
expression_statement.expression.accept(self)
649
elif is_last /\ isa Statements.IF(s) then
650
emit_if_expression(cast Statements.IF(s))
651
elif is_last /\ isa Statements.CASE(s) then
652
emit_case_expression(cast Statements.CASE(s))
653
elif is_last /\ isa Statements.FOR(s) then
654
emit_for_expression(cast Statements.FOR(s))
655
elif is_last /\ isa Statements.DO(s) then
656
emit_do_expression(cast Statements.DO(s))
657
else
658
s.accept(self)
659
fi
660
i = i + 1
661
od
662
si
663
664
// Loop expressions render like their statement forms but end at
665
// the closing keyword with no trailing `;` — the consuming
666
// context owns statement termination — and the body's value is
667
// its last bare expression, as in an if-expression branch.
668
emit_for_expression(for_statement: Statements.FOR) is
669
write("for ")
670
671
if let for_statement.variable? then
672
variable.accept(self)
673
fi
674
675
write(" in ")
676
677
if let for_statement.expression? then
678
expression.accept(self)
679
fi
680
681
write_line(" do")
682
indent()
683
684
if let for_statement.body? then
685
body.accept(self)
686
fi
687
688
outdent()
689
690
// Nested blocks each end with their own terminator, and the inner
691
// one leaves the line open: `od` must not run onto it.
692
if !at_line_start then
693
write_line()
694
fi
695
696
write("od")
697
si
698
699
emit_do_expression(do_statement: Statements.DO) is
700
if let do_statement.binding? then
701
write("while let ")
702
binding.accept(self)
703
write(" ")
704
elif let do_statement.condition? then
705
write("while ")
706
condition.accept(self)
707
write(" ")
708
fi
709
710
write_line("do")
711
indent()
712
do_statement.body.accept(self)
713
outdent()
714
715
// Nested blocks each end with their own terminator, and the inner
716
// one leaves the line open: `od` must not run onto it.
717
if !at_line_start then
718
write_line()
719
fi
720
721
write("od")
722
si
723
724
// --- faithfulness fix: `case` statement / expression. The base
725
// printer emits a legacy `when X :` / `default` / `esac`
726
// shape; the parser accepts only `when X then` / `else` /
727
// `esac`. Binding-pattern arms (`when v: int then`) and
728
// literal-leaf patterns live on the arm's `pattern` field
729
// rather than its `expressions` list and must be emitted
730
// from there. ---
731
732
visit(`case: Statements.CASE) is
733
emit_case_header(`case)
734
735
let i mut = 0
736
for m in `case.matches do
737
// A comment after an arm's last statement belongs inside the
738
// arm: bound the flush by the next arm's keyword (or the
739
// `esac`), or it trails the next keyword's line.
740
let limit =
741
if i + 1 < `case.matches.count then
742
`case.matches[i + 1].location.start
743
else
744
`case.location.end
745
fi
746
747
emit_case_match_at(m, false, limit)
748
i = i + 1
749
od
750
751
// An arm ending in a nested block leaves the line open after
752
// that block's terminator: `esac` must not run onto it.
753
if !at_line_start then
754
write_line()
755
fi
756
757
write_line("esac")
758
si
759
760
visit(match: Statements.CASE_MATCH) is
761
emit_case_match_at(match, false, match.location.end)
762
si
763
764
emit_case_header(case_statement: Statements.CASE) is
765
note(case_statement.location)
766
write("case ")
767
case_statement.expression.accept(self)
768
write_line()
769
si
770
771
emit_case_match_at(match: Statements.CASE_MATCH, as_expression: bool, limit: int) is
772
note(match.location)
773
774
if let match.pattern? then
775
write("when ")
776
pattern.accept(self)
777
778
if let match.guard? then
779
write(" /\\ ")
780
guard.accept(self)
781
fi
782
783
write_line(" then")
784
elif match.expressions? then
785
write("when ")
786
let first mut = true
787
for e in match.expressions! do
788
if !first then
789
write(", ")
790
fi
791
e.accept(self)
792
first = false
793
od
794
write_line(" then")
795
else
796
write_line("else")
797
fi
798
799
indent()
800
801
if as_expression then
802
emit_branch_value(match.statements)
803
write_line()
804
else
805
match.statements.accept(self)
806
fi
807
808
// A comment after the arm's last statement belongs inside the
809
// arm: consume it while still indented, or it is emitted after
810
// the `esac` - or trails the next arm keyword's line.
811
flush_leading(limit)
812
813
outdent()
814
si
815
816
emit_case_expression(case_statement: Statements.CASE) is
817
emit_case_header(case_statement)
818
819
let i mut = 0
820
for m in case_statement.matches do
821
let limit =
822
if i + 1 < case_statement.matches.count then
823
case_statement.matches[i + 1].location.start
824
else
825
case_statement.location.end
826
fi
827
828
emit_case_match_at(m, true, limit)
829
i = i + 1
830
od
831
832
write("esac")
833
si
834
si
835
si