Skip to content
← Back

src/syntax/process/printer/doc.ghul

1
namespace Syntax.Process.Printer is
2
use Collections;
3
4
// Document intermediate representation for the layout engine.
5
//
6
// A formatter visitor builds a tree of these; DOC_RENDERER turns the tree
7
// into text against a column budget. A GROUP renders flat (its LINEs become
8
// spaces) when it fits the budget, otherwise broken (its LINEs become
9
// newlines). This is what trades horizontal width against vertical sprawl.
10
union DOC is
11
// Empty document.
12
NIL;
13
// Literal text. Must not contain newlines.
14
TEXT(text: string);
15
// Soft break: a space when the enclosing group is flat, a newline when
16
// broken.
17
LINE;
18
// Soft break with no flat-mode content: nothing when flat, a newline
19
// when broken.
20
SOFT_LINE;
21
// Unconditional newline. Forces every enclosing group to break.
22
HARD_LINE;
23
// Sequence of documents.
24
CONCAT(items: LIST[DOC]);
25
// Indent the contained document by `indent` extra columns when it
26
// breaks.
27
NEST(indent: int, doc: DOC);
28
// A breaking unit: rendered flat if it fits the budget, else broken.
29
GROUP(doc: DOC);
30
si
31
32
// One open group/nest frame in DOC_BUILDER's stack.
33
class DOC_FRAME is
34
items: LIST[DOC];
35
is_group: bool;
36
nest: int;
37
38
init(is_group: bool, nest: int) is
39
self.items = LIST[DOC]();
40
self.is_group = is_group;
41
self.nest = nest;
42
si
43
si
44
45
// Imperative builder for a DOC tree. Mirrors the write/indent/outdent shape
46
// of the old string-based printer so the visitor stays readable: emit text
47
// and breaks into the current frame, open a GROUP or NEST with begin_*, and
48
// close it with the matching end_*.
49
class DOC_BUILDER is
50
_stack: LIST[DOC_FRAME];
51
52
init() is
53
_stack = LIST[DOC_FRAME]();
54
_stack.add(DOC_FRAME(false, 0));
55
si
56
57
_top: DOC_FRAME => _stack[_stack.count - 1];
58
59
emit(d: DOC) is
60
_top.items.add(d);
61
si
62
63
text(s: string) is
64
emit(DOC.TEXT(s));
65
si
66
67
line() is
68
emit(DOC.LINE());
69
si
70
71
soft_line() is
72
emit(DOC.SOFT_LINE());
73
si
74
75
hard_line() is
76
emit(DOC.HARD_LINE());
77
si
78
79
begin_group() is
80
_stack.add(DOC_FRAME(true, 0));
81
si
82
83
// Open a nest. The newline that introduces an indented block is
84
// emitted (by the printer) just before the nest opens; absorb it so
85
// it lands inside the nest and is therefore indented — matching the
86
// depth-at-write-time behaviour of the old string printer.
87
begin_nest(amount: int) is
88
let frame = DOC_FRAME(false, amount);
89
let parent = _top;
90
91
if parent.items.count > 0 then
92
let last = parent.items[parent.items.count - 1];
93
if isa DOC.HARD_LINE(last) then
94
parent.items.remove_at(parent.items.count - 1);
95
frame.items.add(last);
96
fi
97
fi
98
99
_stack.add(frame);
100
si
101
102
end_group() is
103
let frame = _top;
104
_stack.remove_at(_stack.count - 1);
105
emit(DOC.GROUP(DOC.CONCAT(frame.items)));
106
si
107
108
// Close a nest. The block's last statement leaves a trailing newline
109
// inside the nest; eject it so the closing keyword that follows sits
110
// at the outer indent.
111
end_nest() is
112
let frame = _top;
113
_stack.remove_at(_stack.count - 1);
114
115
let trailing: DOC? mut = null;
116
if
117
frame.items.count > 0 /\
118
isa DOC.HARD_LINE(frame.items[frame.items.count - 1])
119
then
120
trailing = frame.items[frame.items.count - 1];
121
frame.items.remove_at(frame.items.count - 1);
122
fi
123
124
let result: DOC? mut = DOC.CONCAT(frame.items);
125
if frame.nest != 0 then
126
result = DOC.NEST(frame.nest, result);
127
fi
128
emit(result);
129
130
if trailing? then
131
emit(trailing);
132
fi
133
si
134
135
build() -> DOC => DOC.CONCAT(_stack[0].items);
136
si
137
si