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