Skip to content
← Back

src/syntax/process/printer/doc_renderer.ghul

1
namespace Syntax.Process.Printer is
2
use Collections;
3
4
// One unit of pending work for the renderer: a document to lay out at a
5
// given indent, in flat (`flat`) or broken mode.
6
class RENDER_CMD is
7
indent: int;
8
flat: bool;
9
doc: DOC;
10
11
init(indent: int, flat: bool, doc: DOC) is
12
self.indent = indent;
13
self.flat = flat;
14
self.doc = doc;
15
si
16
si
17
18
// Renders a DOC tree to text against a column budget.
19
//
20
// Classic Wadler/Prettier algorithm: a worklist of RENDER_CMDs is consumed
21
// depth-first; at each GROUP, `_fits` measures whether the group rendered
22
// flat — together with whatever follows it on the same line — stays within
23
// the budget, and picks flat or broken accordingly.
24
class DOC_RENDERER is
25
_width: int;
26
27
init(width: int) is
28
_width = width;
29
si
30
31
render(doc: DOC) -> string is
32
let out = System.Text.StringBuilder();
33
let cmds = LIST[RENDER_CMD]();
34
35
cmds.add(RENDER_CMD(0, false, doc));
36
37
let pos mut = 0;
38
39
while cmds.count > 0 do
40
let c = cmds[cmds.count - 1];
41
cmds.remove_at(cmds.count - 1);
42
43
let d = c.doc;
44
45
if let t: DOC.TEXT = d then
46
out.append(t.text);
47
pos = pos + t.text.length;
48
fi
49
50
if let concat: DOC.CONCAT = d then
51
let i mut = concat.items.count - 1;
52
while i >= 0 do
53
cmds.add(RENDER_CMD(c.indent, c.flat, concat.items[i]));
54
i = i - 1;
55
od
56
fi
57
58
if let nest: DOC.NEST = d then
59
cmds.add(RENDER_CMD(c.indent + nest.indent, c.flat, nest.doc));
60
fi
61
62
if let group: DOC.GROUP = d then
63
let flat mut = c.flat;
64
if !flat then
65
flat = _fits(_width - pos, c.indent, group.doc, cmds);
66
fi
67
cmds.add(RENDER_CMD(c.indent, flat, group.doc));
68
fi
69
70
if isa DOC.LINE(d) then
71
if c.flat then
72
out.append(' ');
73
pos = pos + 1;
74
else
75
pos = _newline(out, c.indent);
76
fi
77
fi
78
79
if isa DOC.SOFT_LINE(d) then
80
if !c.flat then
81
pos = _newline(out, c.indent);
82
fi
83
fi
84
85
if isa DOC.HARD_LINE(d) then
86
pos = _newline(out, c.indent);
87
fi
88
od
89
90
return out.to_string();
91
si
92
93
_newline(out: System.Text.StringBuilder, indent: int) -> int is
94
out.append('\n');
95
96
let i mut = 0;
97
while i < indent do
98
out.append(' ');
99
i = i + 1;
100
od
101
102
return indent;
103
si
104
105
// True if `doc` rendered flat, plus everything queued in `rest` up to
106
// the next break, stays within `width` columns. A HARD_LINE inside
107
// `doc` returns false so any group containing a forced break is laid
108
// out broken; a break reached in `rest` ends the line and returns true.
109
_fits(width: int, indent: int, doc: DOC, rest: LIST[RENDER_CMD]) -> bool is
110
let remaining mut = width;
111
112
let local = LIST[RENDER_CMD]();
113
local.add(RENDER_CMD(indent, true, doc));
114
115
while local.count > 0 do
116
if remaining < 0 then
117
return false;
118
fi
119
120
let c = local[local.count - 1];
121
local.remove_at(local.count - 1);
122
123
let d = c.doc;
124
125
if let t: DOC.TEXT = d then
126
remaining = remaining - t.text.length;
127
fi
128
129
if let concat: DOC.CONCAT = d then
130
let i mut = concat.items.count - 1;
131
while i >= 0 do
132
local.add(RENDER_CMD(c.indent, true, concat.items[i]));
133
i = i - 1;
134
od
135
fi
136
137
if let nest: DOC.NEST = d then
138
local.add(RENDER_CMD(c.indent + nest.indent, true, nest.doc));
139
fi
140
141
if let group: DOC.GROUP = d then
142
local.add(RENDER_CMD(c.indent, true, group.doc));
143
fi
144
145
if isa DOC.LINE(d) then
146
remaining = remaining - 1;
147
fi
148
149
if isa DOC.HARD_LINE(d) then
150
return false;
151
fi
152
od
153
154
let rest_index mut = rest.count - 1;
155
let pending = LIST[RENDER_CMD]();
156
157
do
158
if remaining < 0 then
159
return false;
160
fi
161
162
if pending.count == 0 /\ rest_index < 0 then
163
return true;
164
fi
165
166
let c =
167
if pending.count > 0 then
168
pending[pending.count - 1]
169
else
170
rest[rest_index]
171
fi;
172
173
if pending.count > 0 then
174
pending.remove_at(pending.count - 1);
175
else
176
rest_index = rest_index - 1;
177
fi
178
179
let d = c.doc;
180
181
if let t: DOC.TEXT = d then
182
remaining = remaining - t.text.length;
183
fi
184
185
if let concat: DOC.CONCAT = d then
186
let i mut = concat.items.count - 1;
187
while i >= 0 do
188
pending.add(RENDER_CMD(c.indent, c.flat, concat.items[i]));
189
i = i - 1;
190
od
191
fi
192
193
if let nest: DOC.NEST = d then
194
pending.add(RENDER_CMD(c.indent + nest.indent, c.flat, nest.doc));
195
fi
196
197
if let group: DOC.GROUP = d then
198
pending.add(RENDER_CMD(c.indent, c.flat, group.doc));
199
fi
200
201
if isa DOC.LINE(d) then
202
if c.flat then
203
remaining = remaining - 1;
204
else
205
return true;
206
fi
207
fi
208
209
if isa DOC.SOFT_LINE(d) then
210
if !c.flat then
211
return true;
212
fi
213
fi
214
215
if isa DOC.HARD_LINE(d) then
216
return true;
217
fi
218
od
219
si
220
si
221
si