-
Notifications
You must be signed in to change notification settings - Fork 1
/
document.go
260 lines (229 loc) · 7.79 KB
/
document.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
// Copyright 2018 The Cockroach Authors.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
// Package pretty prints documents based on a target line width.
//
// See: https://homepages.inf.ed.ac.uk/wadler/papers/prettier/prettier.pdf
//
// The paper describes a simple algorithm for printing a document tree with a
// single layout defined by text, newlines, and indentation. This concept is
// then expanded for documents that have more than one possible layout using
// a union type of two documents where both documents reduce to the same
// document, but one document has been flattened to a single line. A method
// ("best") is described that chooses the best of these two layouts (i.e.,
// it removes all union types from a document tree). It works by tracking
// the desired and current line length and choosing either the flattened
// side of a union if it fits on the current line, or else the non-flattened
// side. The paper then describes various performance improvements that reduce
// the search space of the best function such that it can complete in O(n)
// instead of O(n^2) time, where n is the number of nodes.
//
// For example code with SQL to experiment further, refer to
// https://github.com/knz/prettier/
package pretty
import "fmt"
// Doc represents a document as described by the type "DOC" in the
// referenced paper. This is the abstract representation constructed
// by the pretty-printing code.
type Doc interface {
isDoc()
}
func (text) isDoc() {}
func (line) isDoc() {}
func (softbreak) isDoc() {}
func (hardline) isDoc() {}
func (nilDoc) isDoc() {}
func (*concat) isDoc() {}
func (nestt) isDoc() {}
func (nests) isDoc() {}
func (*union) isDoc() {}
func (*scolumn) isDoc() {}
func (*snesting) isDoc() {}
func (pad) isDoc() {}
func (keyword) isDoc() {}
//
// Implementations of Doc ("DOC" in paper).
//
// nilDoc represents NIL :: DOC -- the empty doc.
type nilDoc struct{}
// Nil is the NIL constructor.
var Nil Doc = nilDoc{}
// text represents (TEXT s) :: DOC -- a simple text string.
type text string
// Text is the TEXT constructor.
func Text(s string) Doc {
return text(s)
}
// line represents LINE :: DOC -- a "soft line" that can be flattened to a space.
type line struct{}
// Line is a newline and is flattened to a space.
var Line Doc = line{}
// softbreak represents SOFTBREAK :: DOC -- an invisible space between
// words that tries to break the text across lines.
//
// For example, text "hello" <> softbreak <> text "world"
// flattens to "helloworld" (one word) but splits across lines as:
//
// hello
// world
//
// This is a common extension to Wadler's printer.
//
// Idea borrowed from Daniel Mendler's printer at
// https://github.com/minad/wl-pprint-annotated/blob/master/src/Text/PrettyPrint/Annotated/WL.hs
type softbreak struct{}
// SoftBreak is a newline and is flattened to an empty string.
var SoftBreak Doc = softbreak{}
type hardline struct{}
// HardLine is a newline and cannot be flattened.
var HardLine Doc = hardline{}
// concat represents (DOC <> DOC) :: DOC -- the concatenation of two docs.
type concat struct {
a, b Doc
}
// Concat is the <> constructor.
// This uses simplifyNil to avoid actually inserting NIL docs
// in the abstract tree.
func Concat(a, b Doc) Doc {
return simplifyNil(a, b, func(a, b Doc) Doc { return &concat{a, b} })
}
// nests represents (NESTS Int DOC) :: DOC -- nesting a doc "under" another.
// NESTS indents d with n spaces.
// This is more or less exactly the NEST operator in Wadler's printer.
type nests struct {
n int16
d Doc
}
// NestS is the NESTS constructor.
func NestS(n int16, d Doc) Doc {
return nests{n, d}
}
// nestt represents (NESTT DOC) :: DOC -- nesting a doc "under" another
// NESTT indents d with a tab character.
// This is a variant of the NEST operator in Wadler's printer.
type nestt struct {
d Doc
}
// NestT is the NESTT constructor.
func NestT(d Doc) Doc {
return nestt{d}
}
// union represents (DOC <|> DOC) :: DOC -- the union of two docs.
// <|> is really the union of two sets of layouts. x and y must flatten to the
// same layout. Additionally, no first line of a document in x is shorter
// than some first line of a document in y; or, equivalently, every first
// line in x is at least as long as every first line in y.
//
// The main use of the union is via the Group operator defined below.
//
// We do not provide a public constructor as this type is not
// exported.
type union struct {
x, y Doc
}
// Group will format d on one line if possible.
func Group(d Doc) Doc {
return &union{flatten(d), d}
}
var textSpace = Text(" ")
func flatten(d Doc) Doc {
switch t := d.(type) {
case nilDoc:
return Nil
case *concat:
return Concat(flatten(t.a), flatten(t.b))
case nestt:
return NestT(flatten(t.d))
case nests:
return NestS(t.n, flatten(t.d))
case text, keyword, hardline:
return d
case line:
return textSpace
case softbreak:
return Nil
case *union:
return flatten(t.x)
case *scolumn:
return &scolumn{f: func(c int16) Doc { return flatten(t.f(c)) }}
case *snesting:
return &snesting{f: func(i int16) Doc { return flatten(t.f(i)) }}
case pad:
return Nil
default:
panic(fmt.Errorf("unknown type: %T", d))
}
}
// scolumn is a special document which is replaced during rendering by
// another document depending on the current relative column on the
// rendering line (tab prefix excluded).
//
// It is an extension to the Wadler printer commonly found in
// derivative code. See e.g. use by Daniel Mendler in
// https://github.com/minad/wl-pprint-annotated/blob/master/src/Text/PrettyPrint/Annotated/WL.hs
//
// This type is not exposed, see the Align() operator below instead.
type scolumn struct {
f func(int16) Doc
}
// snesting is a special document which is replaced during rendering
// by another document depending on the current space-based nesting
// level (the one added by NestS).
//
// It is an extension to the Wadler printer commonly found in
// derivative code. See e.g. use by Daniel Mendler in
// https://github.com/minad/wl-pprint-annotated/blob/master/src/Text/PrettyPrint/Annotated/WL.hs
//
// This type is not exposed, see the Align() operator below instead.
type snesting struct {
f func(int16) Doc
}
// Align renders document d with the space-based nesting level set to
// the current column.
func Align(d Doc) Doc {
return &scolumn{
f: func(k int16) Doc {
return &snesting{
f: func(i int16) Doc {
return nests{k - i, d}
},
}
},
}
}
// pad is a special document which is replaced during rendering by
// the specified amount of whitespace. However it is flattened
// to an empty document during grouping.
//
// This is an extension to Wadler's printer first prototyped in
// https://github.com/knz/prettier.
//
// Note that this special document must be handled especially
// carefully with anything that produces a union (<|>) (e.g. Group),
// so as to preserve the invariant of unions: "no first line of a
// document in x is shorter than some first line of a document in y;
// or, equivalently, every first line in x is at least as long as
// every first line in y".
//
// The operator RLTable, defined in util.go, is properly careful about
// this.
//
// This document type is not exposed publicly because of the risk
// described above.
type pad struct {
n int16
}
type keyword string
// Keyword is identical to Text except they are filtered by
// keywordTransform. The computed width is always len(s), regardless of
// the result of the result of the transform. This allows for things like
// coloring and other control characters in the output.
func Keyword(s string) Doc {
return keyword(s)
}