1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
1 |
// Copyright 2009 The Go Authors. All rights reserved.
|
2 |
// Use of this source code is governed by a BSD-style
|
|
3 |
// license that can be found in the LICENSE file.
|
|
4 |
||
1.1.2
by Ondřej Surý
Import upstream version 2011.04.27 |
5 |
// Package printer implements printing of AST nodes.
|
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
6 |
package printer |
7 |
||
8 |
import ( |
|
9 |
"fmt"
|
|
10 |
"go/ast"
|
|
11 |
"go/token"
|
|
12 |
"io"
|
|
13 |
"os"
|
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
14 |
"strconv"
|
15 |
"strings"
|
|
16 |
"text/tabwriter"
|
|
1.2.1
by Michael Stapelberg
Import upstream version 1.1~hg20130304 |
17 |
"unicode"
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
18 |
)
|
19 |
||
20 |
const ( |
|
21 |
maxNewlines = 2 // max. number of newlines between source text |
|
22 |
debug = false // enable for debugging |
|
23 |
infinity = 1 << 30 |
|
24 |
)
|
|
25 |
||
26 |
type whiteSpace byte |
|
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
27 |
|
28 |
const ( |
|
29 |
ignore = whiteSpace(0) |
|
30 |
blank = whiteSpace(' ') |
|
31 |
vtab = whiteSpace('\v') |
|
32 |
newline = whiteSpace('\n') |
|
33 |
formfeed = whiteSpace('\f') |
|
34 |
indent = whiteSpace('>') |
|
35 |
unindent = whiteSpace('<') |
|
36 |
)
|
|
37 |
||
38 |
// A pmode value represents the current printer mode.
|
|
39 |
type pmode int |
|
40 |
||
41 |
const ( |
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
42 |
noExtraLinebreak pmode = 1 << iota |
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
43 |
)
|
44 |
||
45 |
type printer struct { |
|
46 |
// Configuration (does not change after initialization)
|
|
47 |
Config
|
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
48 |
fset *token.FileSet |
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
49 |
|
50 |
// Current state
|
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
51 |
output []byte // raw printer result |
52 |
indent int // current indentation |
|
53 |
mode pmode // current printer mode |
|
54 |
impliedSemi bool // if set, a linebreak implies a semicolon |
|
55 |
lastTok token.Token // the last token printed (token.ILLEGAL if it's whitespace) |
|
56 |
wsbuf []whiteSpace // delayed white space |
|
57 |
||
58 |
// Positions
|
|
59 |
// The out position differs from the pos position when the result
|
|
60 |
// formatting differs from the source formatting (in the amount of
|
|
61 |
// white space). If there's a difference and SourcePos is set in
|
|
62 |
// ConfigMode, //line comments are used in the output to restore
|
|
63 |
// original source positions for a reader.
|
|
64 |
pos token.Position // current position in AST (source) space |
|
65 |
out token.Position // current position in output space |
|
66 |
last token.Position // value of pos after calling writeString |
|
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
67 |
|
68 |
// The list of all source comments, in order of appearance.
|
|
69 |
comments []*ast.CommentGroup // may be nil |
|
70 |
cindex int // current comment index |
|
71 |
useNodeComments bool // if not set, ignore lead and line comments of nodes |
|
1.1.1
by Ondřej Surý
Import upstream version 2011.04.13 |
72 |
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
73 |
// Information about p.comments[p.cindex]; set up by nextComment.
|
74 |
comment *ast.CommentGroup // = p.comments[p.cindex]; or nil |
|
75 |
commentOffset int // = p.posFor(p.comments[p.cindex].List[0].Pos()).Offset; or infinity |
|
76 |
commentNewline bool // true if the comment group contains newlines |
|
77 |
||
1.1.1
by Ondřej Surý
Import upstream version 2011.04.13 |
78 |
// Cache of already computed node sizes.
|
79 |
nodeSizes map[ast.Node]int |
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
80 |
|
81 |
// Cache of most recently computed line position.
|
|
82 |
cachedPos token.Pos |
|
83 |
cachedLine int // line corresponding to cachedPos |
|
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
84 |
}
|
85 |
||
1.1.13
by Ondřej Surý
Import upstream version 1 |
86 |
func (p *printer) init(cfg *Config, fset *token.FileSet, nodeSizes map[ast.Node]int) { |
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
87 |
p.Config = *cfg |
88 |
p.fset = fset |
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
89 |
p.pos = token.Position{Line: 1, Column: 1} |
90 |
p.out = token.Position{Line: 1, Column: 1} |
|
1.1.1
by Ondřej Surý
Import upstream version 2011.04.13 |
91 |
p.wsbuf = make([]whiteSpace, 0, 16) // whitespace sequences are short |
92 |
p.nodeSizes = nodeSizes |
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
93 |
p.cachedPos = -1 |
94 |
}
|
|
95 |
||
96 |
// commentsHaveNewline reports whether a list of comments belonging to
|
|
97 |
// an *ast.CommentGroup contains newlines. Because the position information
|
|
98 |
// may only be partially correct, we also have to read the comment text.
|
|
99 |
func (p *printer) commentsHaveNewline(list []*ast.Comment) bool { |
|
100 |
// len(list) > 0
|
|
101 |
line := p.lineFor(list[0].Pos()) |
|
102 |
for i, c := range list { |
|
103 |
if i > 0 && p.lineFor(list[i].Pos()) != line { |
|
104 |
// not all comments on the same line
|
|
105 |
return true |
|
106 |
}
|
|
107 |
if t := c.Text; len(t) >= 2 && (t[1] == '/' || strings.Contains(t, "\n")) { |
|
108 |
return true |
|
109 |
}
|
|
110 |
}
|
|
111 |
_ = line |
|
112 |
return false |
|
113 |
}
|
|
114 |
||
115 |
func (p *printer) nextComment() { |
|
116 |
for p.cindex < len(p.comments) { |
|
117 |
c := p.comments[p.cindex] |
|
118 |
p.cindex++ |
|
119 |
if list := c.List; len(list) > 0 { |
|
120 |
p.comment = c |
|
121 |
p.commentOffset = p.posFor(list[0].Pos()).Offset |
|
122 |
p.commentNewline = p.commentsHaveNewline(list) |
|
123 |
return
|
|
124 |
}
|
|
125 |
// we should not reach here (correct ASTs don't have empty
|
|
126 |
// ast.CommentGroup nodes), but be conservative and try again
|
|
127 |
}
|
|
128 |
// no more comments
|
|
129 |
p.commentOffset = infinity |
|
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
130 |
}
|
131 |
||
132 |
func (p *printer) internalError(msg ...interface{}) { |
|
133 |
if debug { |
|
134 |
fmt.Print(p.pos.String() + ": ") |
|
135 |
fmt.Println(msg...) |
|
136 |
panic("go/printer") |
|
137 |
}
|
|
138 |
}
|
|
139 |
||
1.1.13
by Ondřej Surý
Import upstream version 1 |
140 |
func (p *printer) posFor(pos token.Pos) token.Position { |
141 |
// not used frequently enough to cache entire token.Position
|
|
142 |
return p.fset.Position(pos) |
|
143 |
}
|
|
144 |
||
145 |
func (p *printer) lineFor(pos token.Pos) int { |
|
146 |
if pos != p.cachedPos { |
|
147 |
p.cachedPos = pos |
|
148 |
p.cachedLine = p.fset.Position(pos).Line |
|
149 |
}
|
|
150 |
return p.cachedLine |
|
151 |
}
|
|
152 |
||
153 |
// atLineBegin emits a //line comment if necessary and prints indentation.
|
|
154 |
func (p *printer) atLineBegin(pos token.Position) { |
|
155 |
// write a //line comment if necessary
|
|
156 |
if p.Config.Mode&SourcePos != 0 && pos.IsValid() && (p.out.Line != pos.Line || p.out.Filename != pos.Filename) { |
|
157 |
p.output = append(p.output, tabwriter.Escape) // protect '\n' in //line from tabwriter interpretation |
|
158 |
p.output = append(p.output, fmt.Sprintf("//line %s:%d\n", pos.Filename, pos.Line)...) |
|
159 |
p.output = append(p.output, tabwriter.Escape) |
|
160 |
// p.out must match the //line comment
|
|
161 |
p.out.Filename = pos.Filename |
|
162 |
p.out.Line = pos.Line |
|
163 |
}
|
|
164 |
||
165 |
// write indentation
|
|
166 |
// use "hard" htabs - indentation columns
|
|
167 |
// must not be discarded by the tabwriter
|
|
1.2.1
by Michael Stapelberg
Import upstream version 1.1~hg20130304 |
168 |
n := p.Config.Indent + p.indent // include base indentation |
169 |
for i := 0; i < n; i++ { |
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
170 |
p.output = append(p.output, '\t') |
171 |
}
|
|
172 |
||
173 |
// update positions
|
|
1.2.1
by Michael Stapelberg
Import upstream version 1.1~hg20130304 |
174 |
p.pos.Offset += n |
175 |
p.pos.Column += n |
|
176 |
p.out.Column += n |
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
177 |
}
|
178 |
||
179 |
// writeByte writes ch n times to p.output and updates p.pos.
|
|
180 |
func (p *printer) writeByte(ch byte, n int) { |
|
181 |
if p.out.Column == 1 { |
|
182 |
p.atLineBegin(p.pos) |
|
183 |
}
|
|
184 |
||
185 |
for i := 0; i < n; i++ { |
|
186 |
p.output = append(p.output, ch) |
|
187 |
}
|
|
188 |
||
189 |
// update positions
|
|
190 |
p.pos.Offset += n |
|
191 |
if ch == '\n' || ch == '\f' { |
|
192 |
p.pos.Line += n |
|
193 |
p.out.Line += n |
|
194 |
p.pos.Column = 1 |
|
195 |
p.out.Column = 1 |
|
196 |
return
|
|
197 |
}
|
|
198 |
p.pos.Column += n |
|
199 |
p.out.Column += n |
|
200 |
}
|
|
201 |
||
202 |
// writeString writes the string s to p.output and updates p.pos, p.out,
|
|
203 |
// and p.last. If isLit is set, s is escaped w/ tabwriter.Escape characters
|
|
204 |
// to protect s from being interpreted by the tabwriter.
|
|
205 |
//
|
|
206 |
// Note: writeString is only used to write Go tokens, literals, and
|
|
207 |
// comments, all of which must be written literally. Thus, it is correct
|
|
208 |
// to always set isLit = true. However, setting it explicitly only when
|
|
209 |
// needed (i.e., when we don't know that s contains no tabs or line breaks)
|
|
210 |
// avoids processing extra escape characters and reduces run time of the
|
|
211 |
// printer benchmark by up to 10%.
|
|
212 |
//
|
|
213 |
func (p *printer) writeString(pos token.Position, s string, isLit bool) { |
|
214 |
if p.out.Column == 1 { |
|
215 |
p.atLineBegin(pos) |
|
216 |
}
|
|
217 |
||
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
218 |
if pos.IsValid() { |
1.1.13
by Ondřej Surý
Import upstream version 1 |
219 |
// update p.pos (if pos is invalid, continue with existing p.pos)
|
220 |
// Note: Must do this after handling line beginnings because
|
|
221 |
// atLineBegin updates p.pos if there's indentation, but p.pos
|
|
222 |
// is the position of s.
|
|
223 |
p.pos = pos |
|
224 |
}
|
|
225 |
||
226 |
if isLit { |
|
227 |
// Protect s such that is passes through the tabwriter
|
|
228 |
// unchanged. Note that valid Go programs cannot contain
|
|
229 |
// tabwriter.Escape bytes since they do not appear in legal
|
|
230 |
// UTF-8 sequences.
|
|
231 |
p.output = append(p.output, tabwriter.Escape) |
|
232 |
}
|
|
233 |
||
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
234 |
if debug { |
1.1.13
by Ondřej Surý
Import upstream version 1 |
235 |
p.output = append(p.output, fmt.Sprintf("/*%s*/", pos)...) // do not update p.pos! |
236 |
}
|
|
237 |
p.output = append(p.output, s...) |
|
238 |
||
239 |
// update positions
|
|
240 |
nlines := 0 |
|
241 |
var li int // index of last newline; valid if nlines > 0 |
|
242 |
for i := 0; i < len(s); i++ { |
|
243 |
// Go tokens cannot contain '\f' - no need to look for it
|
|
244 |
if s[i] == '\n' { |
|
245 |
nlines++ |
|
246 |
li = i |
|
247 |
}
|
|
248 |
}
|
|
249 |
p.pos.Offset += len(s) |
|
250 |
if nlines > 0 { |
|
251 |
p.pos.Line += nlines |
|
252 |
p.out.Line += nlines |
|
253 |
c := len(s) - li |
|
254 |
p.pos.Column = c |
|
255 |
p.out.Column = c |
|
256 |
} else { |
|
257 |
p.pos.Column += len(s) |
|
258 |
p.out.Column += len(s) |
|
259 |
}
|
|
260 |
||
261 |
if isLit { |
|
262 |
p.output = append(p.output, tabwriter.Escape) |
|
263 |
}
|
|
264 |
||
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
265 |
p.last = p.pos |
266 |
}
|
|
267 |
||
268 |
// writeCommentPrefix writes the whitespace before a comment.
|
|
269 |
// If there is any pending whitespace, it consumes as much of
|
|
270 |
// it as is likely to help position the comment nicely.
|
|
271 |
// pos is the comment position, next the position of the item
|
|
272 |
// after all pending comments, prev is the previous comment in
|
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
273 |
// a group of comments (or nil), and tok is the next token.
|
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
274 |
//
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
275 |
func (p *printer) writeCommentPrefix(pos, next token.Position, prev, comment *ast.Comment, tok token.Token) { |
276 |
if len(p.output) == 0 { |
|
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
277 |
// the comment is the first item to be printed - don't write any whitespace
|
278 |
return
|
|
279 |
}
|
|
280 |
||
281 |
if pos.IsValid() && pos.Filename != p.last.Filename { |
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
282 |
// comment in a different file - separate with newlines
|
283 |
p.writeByte('\f', maxNewlines) |
|
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
284 |
return
|
285 |
}
|
|
286 |
||
287 |
if pos.Line == p.last.Line && (prev == nil || prev.Text[1] != '/') { |
|
288 |
// comment on the same line as last item:
|
|
289 |
// separate with at least one separator
|
|
290 |
hasSep := false |
|
291 |
if prev == nil { |
|
292 |
// first comment of a comment group
|
|
293 |
j := 0 |
|
1.1.1
by Ondřej Surý
Import upstream version 2011.04.13 |
294 |
for i, ch := range p.wsbuf { |
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
295 |
switch ch { |
296 |
case blank: |
|
297 |
// ignore any blanks before a comment
|
|
1.1.1
by Ondřej Surý
Import upstream version 2011.04.13 |
298 |
p.wsbuf[i] = ignore |
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
299 |
continue
|
300 |
case vtab: |
|
301 |
// respect existing tabs - important
|
|
302 |
// for proper formatting of commented structs
|
|
303 |
hasSep = true |
|
304 |
continue
|
|
305 |
case indent: |
|
306 |
// apply pending indentation
|
|
307 |
continue
|
|
308 |
}
|
|
309 |
j = i |
|
310 |
break
|
|
311 |
}
|
|
312 |
p.writeWhitespace(j) |
|
313 |
}
|
|
314 |
// make sure there is at least one separator
|
|
315 |
if !hasSep { |
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
316 |
sep := byte('\t') |
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
317 |
if pos.Line == next.Line { |
318 |
// next item is on the same line as the comment
|
|
319 |
// (which must be a /*-style comment): separate
|
|
320 |
// with a blank instead of a tab
|
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
321 |
sep = ' ' |
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
322 |
}
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
323 |
p.writeByte(sep, 1) |
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
324 |
}
|
325 |
||
326 |
} else { |
|
327 |
// comment on a different line:
|
|
328 |
// separate with at least one line break
|
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
329 |
droppedLinebreak := false |
330 |
j := 0 |
|
331 |
for i, ch := range p.wsbuf { |
|
332 |
switch ch { |
|
333 |
case blank, vtab: |
|
334 |
// ignore any horizontal whitespace before line breaks
|
|
335 |
p.wsbuf[i] = ignore |
|
336 |
continue
|
|
337 |
case indent: |
|
338 |
// apply pending indentation
|
|
339 |
continue
|
|
340 |
case unindent: |
|
341 |
// if this is not the last unindent, apply it
|
|
342 |
// as it is (likely) belonging to the last
|
|
343 |
// construct (e.g., a multi-line expression list)
|
|
344 |
// and is not part of closing a block
|
|
345 |
if i+1 < len(p.wsbuf) && p.wsbuf[i+1] == unindent { |
|
346 |
continue
|
|
347 |
}
|
|
348 |
// if the next token is not a closing }, apply the unindent
|
|
349 |
// if it appears that the comment is aligned with the
|
|
350 |
// token; otherwise assume the unindent is part of a
|
|
351 |
// closing block and stop (this scenario appears with
|
|
352 |
// comments before a case label where the comments
|
|
353 |
// apply to the next case instead of the current one)
|
|
354 |
if tok != token.RBRACE && pos.Column == next.Column { |
|
355 |
continue
|
|
356 |
}
|
|
357 |
case newline, formfeed: |
|
358 |
p.wsbuf[i] = ignore |
|
359 |
droppedLinebreak = prev == nil // record only if first comment of a group |
|
360 |
}
|
|
361 |
j = i |
|
362 |
break
|
|
363 |
}
|
|
364 |
p.writeWhitespace(j) |
|
365 |
||
366 |
// determine number of linebreaks before the comment
|
|
367 |
n := 0 |
|
368 |
if pos.IsValid() && p.last.IsValid() { |
|
369 |
n = pos.Line - p.last.Line |
|
370 |
if n < 0 { // should never happen |
|
371 |
n = 0 |
|
372 |
}
|
|
373 |
}
|
|
374 |
||
375 |
// at the package scope level only (p.indent == 0),
|
|
376 |
// add an extra newline if we dropped one before:
|
|
377 |
// this preserves a blank line before documentation
|
|
378 |
// comments at the package scope level (issue 2570)
|
|
379 |
if p.indent == 0 && droppedLinebreak { |
|
380 |
n++ |
|
381 |
}
|
|
382 |
||
383 |
// make sure there is at least one line break
|
|
384 |
// if the previous comment was a line comment
|
|
385 |
if n == 0 && prev != nil && prev.Text[1] == '/' { |
|
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
386 |
n = 1 |
387 |
}
|
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
388 |
|
389 |
if n > 0 { |
|
390 |
// use formfeeds to break columns before a comment;
|
|
391 |
// this is analogous to using formfeeds to separate
|
|
392 |
// individual lines of /*-style comments
|
|
393 |
p.writeByte('\f', nlimit(n)) |
|
394 |
}
|
|
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
395 |
}
|
396 |
}
|
|
397 |
||
1.1.13
by Ondřej Surý
Import upstream version 1 |
398 |
// Returns true if s contains only white space
|
399 |
// (only tabs and blanks can appear in the printer's context).
|
|
1.2.1
by Michael Stapelberg
Import upstream version 1.1~hg20130304 |
400 |
//
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
401 |
func isBlank(s string) bool { |
402 |
for i := 0; i < len(s); i++ { |
|
403 |
if s[i] > ' ' { |
|
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
404 |
return false |
405 |
}
|
|
406 |
}
|
|
407 |
return true |
|
408 |
}
|
|
409 |
||
1.2.1
by Michael Stapelberg
Import upstream version 1.1~hg20130304 |
410 |
// commonPrefix returns the common prefix of a and b.
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
411 |
func commonPrefix(a, b string) string { |
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
412 |
i := 0 |
413 |
for i < len(a) && i < len(b) && a[i] == b[i] && (a[i] <= ' ' || a[i] == '*') { |
|
414 |
i++ |
|
415 |
}
|
|
416 |
return a[0:i] |
|
417 |
}
|
|
418 |
||
1.2.1
by Michael Stapelberg
Import upstream version 1.1~hg20130304 |
419 |
// trimRight returns s with trailing whitespace removed.
|
420 |
func trimRight(s string) string { |
|
421 |
return strings.TrimRightFunc(s, unicode.IsSpace) |
|
422 |
}
|
|
423 |
||
424 |
// stripCommonPrefix removes a common prefix from /*-style comment lines (unless no
|
|
425 |
// comment line is indented, all but the first line have some form of space prefix).
|
|
426 |
// The prefix is computed using heuristics such that is likely that the comment
|
|
427 |
// contents are nicely laid out after re-printing each line using the printer's
|
|
428 |
// current indentation.
|
|
429 |
//
|
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
430 |
func stripCommonPrefix(lines []string) { |
1.2.1
by Michael Stapelberg
Import upstream version 1.1~hg20130304 |
431 |
if len(lines) <= 1 { |
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
432 |
return // at most one line - nothing to do |
433 |
}
|
|
1.2.1
by Michael Stapelberg
Import upstream version 1.1~hg20130304 |
434 |
// len(lines) > 1
|
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
435 |
|
436 |
// The heuristic in this function tries to handle a few
|
|
437 |
// common patterns of /*-style comments: Comments where
|
|
438 |
// the opening /* and closing */ are aligned and the
|
|
439 |
// rest of the comment text is aligned and indented with
|
|
440 |
// blanks or tabs, cases with a vertical "line of stars"
|
|
441 |
// on the left, and cases where the closing */ is on the
|
|
442 |
// same line as the last comment text.
|
|
443 |
||
444 |
// Compute maximum common white prefix of all but the first,
|
|
445 |
// last, and blank lines, and replace blank lines with empty
|
|
446 |
// lines (the first line starts with /* and has no prefix).
|
|
447 |
// In case of two-line comments, consider the last line for
|
|
448 |
// the prefix computation since otherwise the prefix would
|
|
449 |
// be empty.
|
|
450 |
//
|
|
451 |
// Note that the first and last line are never empty (they
|
|
452 |
// contain the opening /* and closing */ respectively) and
|
|
453 |
// thus they can be ignored by the blank line check.
|
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
454 |
var prefix string |
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
455 |
if len(lines) > 2 { |
1.1.13
by Ondřej Surý
Import upstream version 1 |
456 |
first := true |
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
457 |
for i, line := range lines[1 : len(lines)-1] { |
458 |
switch { |
|
459 |
case isBlank(line): |
|
1.2.1
by Michael Stapelberg
Import upstream version 1.1~hg20130304 |
460 |
lines[1+i] = "" // range starts with lines[1] |
1.1.13
by Ondřej Surý
Import upstream version 1 |
461 |
case first: |
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
462 |
prefix = commonPrefix(line, line) |
1.1.13
by Ondřej Surý
Import upstream version 1 |
463 |
first = false |
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
464 |
default: |
465 |
prefix = commonPrefix(prefix, line) |
|
466 |
}
|
|
467 |
}
|
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
468 |
} else { // len(lines) == 2, lines cannot be blank (contain /* and */) |
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
469 |
line := lines[1] |
470 |
prefix = commonPrefix(line, line) |
|
471 |
}
|
|
472 |
||
473 |
/*
|
|
474 |
* Check for vertical "line of stars" and correct prefix accordingly.
|
|
475 |
*/
|
|
476 |
lineOfStars := false |
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
477 |
if i := strings.Index(prefix, "*"); i >= 0 { |
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
478 |
// Line of stars present.
|
479 |
if i > 0 && prefix[i-1] == ' ' { |
|
480 |
i-- // remove trailing blank from prefix so stars remain aligned |
|
481 |
}
|
|
482 |
prefix = prefix[0:i] |
|
483 |
lineOfStars = true |
|
484 |
} else { |
|
485 |
// No line of stars present.
|
|
486 |
// Determine the white space on the first line after the /*
|
|
487 |
// and before the beginning of the comment text, assume two
|
|
488 |
// blanks instead of the /* unless the first character after
|
|
489 |
// the /* is a tab. If the first comment line is empty but
|
|
490 |
// for the opening /*, assume up to 3 blanks or a tab. This
|
|
491 |
// whitespace may be found as suffix in the common prefix.
|
|
492 |
first := lines[0] |
|
493 |
if isBlank(first[2:]) { |
|
494 |
// no comment text on the first line:
|
|
495 |
// reduce prefix by up to 3 blanks or a tab
|
|
496 |
// if present - this keeps comment text indented
|
|
497 |
// relative to the /* and */'s if it was indented
|
|
498 |
// in the first place
|
|
499 |
i := len(prefix) |
|
500 |
for n := 0; n < 3 && i > 0 && prefix[i-1] == ' '; n++ { |
|
501 |
i-- |
|
502 |
}
|
|
503 |
if i == len(prefix) && i > 0 && prefix[i-1] == '\t' { |
|
504 |
i-- |
|
505 |
}
|
|
506 |
prefix = prefix[0:i] |
|
507 |
} else { |
|
508 |
// comment text on the first line
|
|
509 |
suffix := make([]byte, len(first)) |
|
510 |
n := 2 // start after opening /* |
|
511 |
for n < len(first) && first[n] <= ' ' { |
|
512 |
suffix[n] = first[n] |
|
513 |
n++ |
|
514 |
}
|
|
515 |
if n > 2 && suffix[2] == '\t' { |
|
516 |
// assume the '\t' compensates for the /*
|
|
517 |
suffix = suffix[2:n] |
|
518 |
} else { |
|
519 |
// otherwise assume two blanks
|
|
520 |
suffix[0], suffix[1] = ' ', ' ' |
|
521 |
suffix = suffix[0:n] |
|
522 |
}
|
|
523 |
// Shorten the computed common prefix by the length of
|
|
524 |
// suffix, if it is found as suffix of the prefix.
|
|
1.2.1
by Michael Stapelberg
Import upstream version 1.1~hg20130304 |
525 |
prefix = strings.TrimSuffix(prefix, string(suffix)) |
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
526 |
}
|
527 |
}
|
|
528 |
||
529 |
// Handle last line: If it only contains a closing */, align it
|
|
530 |
// with the opening /*, otherwise align the text with the other
|
|
531 |
// lines.
|
|
532 |
last := lines[len(lines)-1] |
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
533 |
closing := "*/" |
534 |
i := strings.Index(last, closing) // i >= 0 (closing is always present) |
|
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
535 |
if isBlank(last[0:i]) { |
536 |
// last line only contains closing */
|
|
537 |
if lineOfStars { |
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
538 |
closing = " */" // add blank to align final star |
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
539 |
}
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
540 |
lines[len(lines)-1] = prefix + closing |
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
541 |
} else { |
542 |
// last line contains more comment text - assume
|
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
543 |
// it is aligned like the other lines and include
|
544 |
// in prefix computation
|
|
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
545 |
prefix = commonPrefix(prefix, last) |
546 |
}
|
|
547 |
||
548 |
// Remove the common prefix from all but the first and empty lines.
|
|
1.2.1
by Michael Stapelberg
Import upstream version 1.1~hg20130304 |
549 |
for i, line := range lines { |
550 |
if i > 0 && line != "" { |
|
551 |
lines[i] = line[len(prefix):] |
|
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
552 |
}
|
553 |
}
|
|
554 |
}
|
|
555 |
||
556 |
func (p *printer) writeComment(comment *ast.Comment) { |
|
557 |
text := comment.Text |
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
558 |
pos := p.posFor(comment.Pos()) |
559 |
||
560 |
const linePrefix = "//line " |
|
561 |
if strings.HasPrefix(text, linePrefix) && (!pos.IsValid() || pos.Column == 1) { |
|
562 |
// possibly a line directive
|
|
563 |
ldir := strings.TrimSpace(text[len(linePrefix):]) |
|
564 |
if i := strings.LastIndex(ldir, ":"); i >= 0 { |
|
565 |
if line, err := strconv.Atoi(ldir[i+1:]); err == nil && line > 0 { |
|
566 |
// The line directive we are about to print changed
|
|
567 |
// the Filename and Line number used for subsequent
|
|
568 |
// tokens. We have to update our AST-space position
|
|
569 |
// accordingly and suspend indentation temporarily.
|
|
570 |
indent := p.indent |
|
571 |
p.indent = 0 |
|
572 |
defer func() { |
|
573 |
p.pos.Filename = ldir[:i] |
|
574 |
p.pos.Line = line |
|
575 |
p.pos.Column = 1 |
|
576 |
p.indent = indent |
|
577 |
}()
|
|
578 |
}
|
|
579 |
}
|
|
580 |
}
|
|
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
581 |
|
582 |
// shortcut common case of //-style comments
|
|
583 |
if text[1] == '/' { |
|
1.2.1
by Michael Stapelberg
Import upstream version 1.1~hg20130304 |
584 |
p.writeString(pos, trimRight(text), true) |
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
585 |
return
|
586 |
}
|
|
587 |
||
588 |
// for /*-style comments, print line by line and let the
|
|
589 |
// write function take care of the proper indentation
|
|
1.2.3
by Michael Stapelberg
Import upstream version 1.1~hg20130405 |
590 |
lines := strings.Split(text, "\n") |
1.2.1
by Michael Stapelberg
Import upstream version 1.1~hg20130304 |
591 |
|
592 |
// The comment started in the first column but is going
|
|
593 |
// to be indented. For an idempotent result, add indentation
|
|
594 |
// to all lines such that they look like they were indented
|
|
595 |
// before - this will make sure the common prefix computation
|
|
596 |
// is the same independent of how many times formatting is
|
|
597 |
// applied (was issue 1835).
|
|
598 |
if pos.IsValid() && pos.Column == 1 && p.indent > 0 { |
|
599 |
for i, line := range lines[1:] { |
|
600 |
lines[1+i] = " " + line |
|
601 |
}
|
|
602 |
}
|
|
603 |
||
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
604 |
stripCommonPrefix(lines) |
605 |
||
606 |
// write comment lines, separated by formfeed,
|
|
607 |
// without a line break after the last line
|
|
608 |
for i, line := range lines { |
|
609 |
if i > 0 { |
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
610 |
p.writeByte('\f', 1) |
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
611 |
pos = p.pos |
612 |
}
|
|
613 |
if len(line) > 0 { |
|
1.2.1
by Michael Stapelberg
Import upstream version 1.1~hg20130304 |
614 |
p.writeString(pos, trimRight(line), true) |
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
615 |
}
|
616 |
}
|
|
617 |
}
|
|
618 |
||
619 |
// writeCommentSuffix writes a line break after a comment if indicated
|
|
620 |
// and processes any leftover indentation information. If a line break
|
|
621 |
// is needed, the kind of break (newline vs formfeed) depends on the
|
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
622 |
// pending whitespace. The writeCommentSuffix result indicates if a
|
623 |
// newline was written or if a formfeed was dropped from the whitespace
|
|
624 |
// buffer.
|
|
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
625 |
//
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
626 |
func (p *printer) writeCommentSuffix(needsLinebreak bool) (wroteNewline, droppedFF bool) { |
1.1.1
by Ondřej Surý
Import upstream version 2011.04.13 |
627 |
for i, ch := range p.wsbuf { |
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
628 |
switch ch { |
629 |
case blank, vtab: |
|
630 |
// ignore trailing whitespace
|
|
1.1.1
by Ondřej Surý
Import upstream version 2011.04.13 |
631 |
p.wsbuf[i] = ignore |
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
632 |
case indent, unindent: |
1.1.6
by Ondřej Surý
Import upstream version 58 |
633 |
// don't lose indentation information
|
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
634 |
case newline, formfeed: |
635 |
// if we need a line break, keep exactly one
|
|
636 |
// but remember if we dropped any formfeeds
|
|
637 |
if needsLinebreak { |
|
638 |
needsLinebreak = false |
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
639 |
wroteNewline = true |
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
640 |
} else { |
641 |
if ch == formfeed { |
|
642 |
droppedFF = true |
|
643 |
}
|
|
1.1.1
by Ondřej Surý
Import upstream version 2011.04.13 |
644 |
p.wsbuf[i] = ignore |
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
645 |
}
|
646 |
}
|
|
647 |
}
|
|
1.1.1
by Ondřej Surý
Import upstream version 2011.04.13 |
648 |
p.writeWhitespace(len(p.wsbuf)) |
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
649 |
|
650 |
// make sure we have a line break
|
|
651 |
if needsLinebreak { |
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
652 |
p.writeByte('\n', 1) |
653 |
wroteNewline = true |
|
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
654 |
}
|
655 |
||
656 |
return
|
|
657 |
}
|
|
658 |
||
659 |
// intersperseComments consumes all comments that appear before the next token
|
|
660 |
// tok and prints it together with the buffered whitespace (i.e., the whitespace
|
|
661 |
// that needs to be written before the next token). A heuristic is used to mix
|
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
662 |
// the comments and whitespace. The intersperseComments result indicates if a
|
663 |
// newline was written or if a formfeed was dropped from the whitespace buffer.
|
|
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
664 |
//
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
665 |
func (p *printer) intersperseComments(next token.Position, tok token.Token) (wroteNewline, droppedFF bool) { |
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
666 |
var last *ast.Comment |
1.1.13
by Ondřej Surý
Import upstream version 1 |
667 |
for p.commentBefore(next) { |
668 |
for _, c := range p.comment.List { |
|
669 |
p.writeCommentPrefix(p.posFor(c.Pos()), next, last, c, tok) |
|
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
670 |
p.writeComment(c) |
671 |
last = c |
|
672 |
}
|
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
673 |
p.nextComment() |
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
674 |
}
|
675 |
||
676 |
if last != nil { |
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
677 |
// if the last comment is a /*-style comment and the next item
|
678 |
// follows on the same line but is not a comma or a "closing"
|
|
679 |
// token, add an extra blank for separation
|
|
680 |
if last.Text[1] == '*' && p.lineFor(last.Pos()) == next.Line && tok != token.COMMA && |
|
681 |
tok != token.RPAREN && tok != token.RBRACK && tok != token.RBRACE { |
|
682 |
p.writeByte(' ', 1) |
|
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
683 |
}
|
684 |
// ensure that there is a line break after a //-style comment,
|
|
685 |
// before a closing '}' unless explicitly disabled, or at eof
|
|
686 |
needsLinebreak := |
|
687 |
last.Text[1] == '/' || |
|
688 |
tok == token.RBRACE && p.mode&noExtraLinebreak == 0 || |
|
689 |
tok == token.EOF |
|
690 |
return p.writeCommentSuffix(needsLinebreak) |
|
691 |
}
|
|
692 |
||
693 |
// no comment was written - we should never reach here since
|
|
694 |
// intersperseComments should not be called in that case
|
|
695 |
p.internalError("intersperseComments called without pending comments") |
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
696 |
return
|
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
697 |
}
|
698 |
||
699 |
// whiteWhitespace writes the first n whitespace entries.
|
|
700 |
func (p *printer) writeWhitespace(n int) { |
|
701 |
// write entries
|
|
702 |
for i := 0; i < n; i++ { |
|
1.1.1
by Ondřej Surý
Import upstream version 2011.04.13 |
703 |
switch ch := p.wsbuf[i]; ch { |
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
704 |
case ignore: |
705 |
// ignore!
|
|
706 |
case indent: |
|
707 |
p.indent++ |
|
708 |
case unindent: |
|
709 |
p.indent-- |
|
710 |
if p.indent < 0 { |
|
711 |
p.internalError("negative indentation:", p.indent) |
|
712 |
p.indent = 0 |
|
713 |
}
|
|
714 |
case newline, formfeed: |
|
715 |
// A line break immediately followed by a "correcting"
|
|
716 |
// unindent is swapped with the unindent - this permits
|
|
717 |
// proper label positioning. If a comment is between
|
|
718 |
// the line break and the label, the unindent is not
|
|
719 |
// part of the comment whitespace prefix and the comment
|
|
720 |
// will be positioned correctly indented.
|
|
1.1.1
by Ondřej Surý
Import upstream version 2011.04.13 |
721 |
if i+1 < n && p.wsbuf[i+1] == unindent { |
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
722 |
// Use a formfeed to terminate the current section.
|
723 |
// Otherwise, a long label name on the next line leading
|
|
724 |
// to a wide column may increase the indentation column
|
|
725 |
// of lines before the label; effectively leading to wrong
|
|
726 |
// indentation.
|
|
1.1.1
by Ondřej Surý
Import upstream version 2011.04.13 |
727 |
p.wsbuf[i], p.wsbuf[i+1] = unindent, formfeed |
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
728 |
i-- // do it again |
729 |
continue
|
|
730 |
}
|
|
731 |
fallthrough
|
|
732 |
default: |
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
733 |
p.writeByte(byte(ch), 1) |
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
734 |
}
|
735 |
}
|
|
736 |
||
737 |
// shift remaining entries down
|
|
738 |
i := 0 |
|
1.1.1
by Ondřej Surý
Import upstream version 2011.04.13 |
739 |
for ; n < len(p.wsbuf); n++ { |
740 |
p.wsbuf[i] = p.wsbuf[n] |
|
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
741 |
i++ |
742 |
}
|
|
1.1.1
by Ondřej Surý
Import upstream version 2011.04.13 |
743 |
p.wsbuf = p.wsbuf[0:i] |
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
744 |
}
|
745 |
||
746 |
// ----------------------------------------------------------------------------
|
|
747 |
// Printing interface
|
|
748 |
||
1.1.13
by Ondřej Surý
Import upstream version 1 |
749 |
// nlines limits n to maxNewlines.
|
750 |
func nlimit(n int) int { |
|
751 |
if n > maxNewlines { |
|
752 |
n = maxNewlines |
|
753 |
}
|
|
754 |
return n |
|
755 |
}
|
|
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
756 |
|
757 |
func mayCombine(prev token.Token, next byte) (b bool) { |
|
758 |
switch prev { |
|
759 |
case token.INT: |
|
760 |
b = next == '.' // 1. |
|
761 |
case token.ADD: |
|
762 |
b = next == '+' // ++ |
|
763 |
case token.SUB: |
|
764 |
b = next == '-' // -- |
|
765 |
case token.QUO: |
|
766 |
b = next == '*' // /* |
|
767 |
case token.LSS: |
|
768 |
b = next == '-' || next == '<' // <- or << |
|
769 |
case token.AND: |
|
770 |
b = next == '&' || next == '^' // && or &^ |
|
771 |
}
|
|
772 |
return
|
|
773 |
}
|
|
774 |
||
775 |
// print prints a list of "items" (roughly corresponding to syntactic
|
|
776 |
// tokens, but also including whitespace and formatting information).
|
|
777 |
// It is the only print function that should be called directly from
|
|
778 |
// any of the AST printing functions in nodes.go.
|
|
779 |
//
|
|
780 |
// Whitespace is accumulated until a non-whitespace token appears. Any
|
|
781 |
// comments that need to appear before that token are printed first,
|
|
782 |
// taking into account the amount and structure of any pending white-
|
|
783 |
// space for best comment placement. Then, any leftover whitespace is
|
|
784 |
// printed, followed by the actual token.
|
|
785 |
//
|
|
786 |
func (p *printer) print(args ...interface{}) { |
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
787 |
for _, arg := range args { |
788 |
// information about the current arg
|
|
1.1.1
by Ondřej Surý
Import upstream version 2011.04.13 |
789 |
var data string |
1.1.13
by Ondřej Surý
Import upstream version 1 |
790 |
var isLit bool |
791 |
var impliedSemi bool // value for p.impliedSemi after this arg |
|
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
792 |
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
793 |
switch x := arg.(type) { |
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
794 |
case pmode: |
795 |
// toggle printer mode
|
|
796 |
p.mode ^= x |
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
797 |
continue
|
798 |
||
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
799 |
case whiteSpace: |
800 |
if x == ignore { |
|
801 |
// don't add ignore's to the buffer; they
|
|
802 |
// may screw up "correcting" unindents (see
|
|
803 |
// LabeledStmt)
|
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
804 |
continue
|
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
805 |
}
|
1.1.1
by Ondřej Surý
Import upstream version 2011.04.13 |
806 |
i := len(p.wsbuf) |
807 |
if i == cap(p.wsbuf) { |
|
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
808 |
// Whitespace sequences are very short so this should
|
809 |
// never happen. Handle gracefully (but possibly with
|
|
810 |
// bad comment placement) if it does happen.
|
|
811 |
p.writeWhitespace(i) |
|
812 |
i = 0 |
|
813 |
}
|
|
1.1.1
by Ondřej Surý
Import upstream version 2011.04.13 |
814 |
p.wsbuf = p.wsbuf[0 : i+1] |
815 |
p.wsbuf[i] = x |
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
816 |
if x == newline || x == formfeed { |
817 |
// newlines affect the current state (p.impliedSemi)
|
|
818 |
// and not the state after printing arg (impliedSemi)
|
|
819 |
// because comments can be interspersed before the arg
|
|
820 |
// in this case
|
|
821 |
p.impliedSemi = false |
|
822 |
}
|
|
823 |
p.lastTok = token.ILLEGAL |
|
824 |
continue
|
|
825 |
||
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
826 |
case *ast.Ident: |
1.1.1
by Ondřej Surý
Import upstream version 2011.04.13 |
827 |
data = x.Name |
1.1.13
by Ondřej Surý
Import upstream version 1 |
828 |
impliedSemi = true |
829 |
p.lastTok = token.IDENT |
|
830 |
||
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
831 |
case *ast.BasicLit: |
1.1.13
by Ondřej Surý
Import upstream version 1 |
832 |
data = x.Value |
833 |
isLit = true |
|
834 |
impliedSemi = true |
|
835 |
p.lastTok = x.Kind |
|
836 |
||
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
837 |
case token.Token: |
838 |
s := x.String() |
|
839 |
if mayCombine(p.lastTok, s[0]) { |
|
840 |
// the previous and the current token must be
|
|
841 |
// separated by a blank otherwise they combine
|
|
842 |
// into a different incorrect token sequence
|
|
843 |
// (except for token.INT followed by a '.' this
|
|
844 |
// should never happen because it is taken care
|
|
845 |
// of via binary expression formatting)
|
|
1.1.1
by Ondřej Surý
Import upstream version 2011.04.13 |
846 |
if len(p.wsbuf) != 0 { |
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
847 |
p.internalError("whitespace buffer not empty") |
848 |
}
|
|
1.1.1
by Ondřej Surý
Import upstream version 2011.04.13 |
849 |
p.wsbuf = p.wsbuf[0:1] |
850 |
p.wsbuf[0] = ' ' |
|
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
851 |
}
|
1.1.1
by Ondřej Surý
Import upstream version 2011.04.13 |
852 |
data = s |
1.1.13
by Ondřej Surý
Import upstream version 1 |
853 |
// some keywords followed by a newline imply a semicolon
|
854 |
switch x { |
|
855 |
case token.BREAK, token.CONTINUE, token.FALLTHROUGH, token.RETURN, |
|
856 |
token.INC, token.DEC, token.RPAREN, token.RBRACK, token.RBRACE: |
|
857 |
impliedSemi = true |
|
858 |
}
|
|
859 |
p.lastTok = x |
|
860 |
||
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
861 |
case token.Pos: |
862 |
if x.IsValid() { |
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
863 |
p.pos = p.posFor(x) // accurate position of next item |
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
864 |
}
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
865 |
continue
|
866 |
||
867 |
case string: |
|
868 |
// incorrect AST - print error message
|
|
869 |
data = x |
|
870 |
isLit = true |
|
871 |
impliedSemi = true |
|
872 |
p.lastTok = token.STRING |
|
873 |
||
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
874 |
default: |
1.1.13
by Ondřej Surý
Import upstream version 1 |
875 |
fmt.Fprintf(os.Stderr, "print: unsupported argument %v (%T)\n", arg, arg) |
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
876 |
panic("go/printer type") |
877 |
}
|
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
878 |
// data != ""
|
879 |
||
880 |
next := p.pos // estimated/accurate position of next item |
|
881 |
wroteNewline, droppedFF := p.flush(next, p.lastTok) |
|
882 |
||
883 |
// intersperse extra newlines if present in the source and
|
|
884 |
// if they don't cause extra semicolons (don't do this in
|
|
885 |
// flush as it will cause extra newlines at the end of a file)
|
|
886 |
if !p.impliedSemi { |
|
887 |
n := nlimit(next.Line - p.pos.Line) |
|
888 |
// don't exceed maxNewlines if we already wrote one
|
|
889 |
if wroteNewline && n == maxNewlines { |
|
890 |
n = maxNewlines - 1 |
|
891 |
}
|
|
892 |
if n > 0 { |
|
893 |
ch := byte('\n') |
|
894 |
if droppedFF { |
|
895 |
ch = '\f' // use formfeed since we dropped one before |
|
896 |
}
|
|
897 |
p.writeByte(ch, n) |
|
898 |
impliedSemi = false |
|
899 |
}
|
|
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
900 |
}
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
901 |
|
902 |
p.writeString(next, data, isLit) |
|
903 |
p.impliedSemi = impliedSemi |
|
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
904 |
}
|
905 |
}
|
|
906 |
||
1.1.13
by Ondřej Surý
Import upstream version 1 |
907 |
// commentBefore returns true iff the current comment group occurs
|
908 |
// before the next position in the source code and printing it does
|
|
909 |
// not introduce implicit semicolons.
|
|
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
910 |
//
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
911 |
func (p *printer) commentBefore(next token.Position) (result bool) { |
912 |
return p.commentOffset < next.Offset && (!p.impliedSemi || !p.commentNewline) |
|
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
913 |
}
|
914 |
||
1.1.13
by Ondřej Surý
Import upstream version 1 |
915 |
// flush prints any pending comments and whitespace occurring textually
|
916 |
// before the position of the next token tok. The flush result indicates
|
|
917 |
// if a newline was written or if a formfeed was dropped from the whitespace
|
|
918 |
// buffer.
|
|
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
919 |
//
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
920 |
func (p *printer) flush(next token.Position, tok token.Token) (wroteNewline, droppedFF bool) { |
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
921 |
if p.commentBefore(next) { |
922 |
// if there are comments before the next item, intersperse them
|
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
923 |
wroteNewline, droppedFF = p.intersperseComments(next, tok) |
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
924 |
} else { |
925 |
// otherwise, write any leftover whitespace
|
|
1.1.1
by Ondřej Surý
Import upstream version 2011.04.13 |
926 |
p.writeWhitespace(len(p.wsbuf)) |
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
927 |
}
|
928 |
return
|
|
929 |
}
|
|
930 |
||
1.1.13
by Ondřej Surý
Import upstream version 1 |
931 |
// getNode returns the ast.CommentGroup associated with n, if any.
|
932 |
func getDoc(n ast.Node) *ast.CommentGroup { |
|
933 |
switch n := n.(type) { |
|
934 |
case *ast.Field: |
|
935 |
return n.Doc |
|
936 |
case *ast.ImportSpec: |
|
937 |
return n.Doc |
|
938 |
case *ast.ValueSpec: |
|
939 |
return n.Doc |
|
940 |
case *ast.TypeSpec: |
|
941 |
return n.Doc |
|
942 |
case *ast.GenDecl: |
|
943 |
return n.Doc |
|
944 |
case *ast.FuncDecl: |
|
945 |
return n.Doc |
|
946 |
case *ast.File: |
|
947 |
return n.Doc |
|
948 |
}
|
|
949 |
return nil |
|
950 |
}
|
|
951 |
||
952 |
func (p *printer) printNode(node interface{}) error { |
|
953 |
// unpack *CommentedNode, if any
|
|
954 |
var comments []*ast.CommentGroup |
|
955 |
if cnode, ok := node.(*CommentedNode); ok { |
|
956 |
node = cnode.Node |
|
957 |
comments = cnode.Comments |
|
958 |
}
|
|
959 |
||
960 |
if comments != nil { |
|
961 |
// commented node - restrict comment list to relevant range
|
|
962 |
n, ok := node.(ast.Node) |
|
963 |
if !ok { |
|
964 |
goto unsupported |
|
965 |
}
|
|
966 |
beg := n.Pos() |
|
967 |
end := n.End() |
|
968 |
// if the node has associated documentation,
|
|
969 |
// include that commentgroup in the range
|
|
970 |
// (the comment list is sorted in the order
|
|
971 |
// of the comment appearance in the source code)
|
|
972 |
if doc := getDoc(n); doc != nil { |
|
973 |
beg = doc.Pos() |
|
974 |
}
|
|
975 |
// token.Pos values are global offsets, we can
|
|
976 |
// compare them directly
|
|
977 |
i := 0 |
|
978 |
for i < len(comments) && comments[i].End() < beg { |
|
979 |
i++ |
|
980 |
}
|
|
981 |
j := i |
|
982 |
for j < len(comments) && comments[j].Pos() < end { |
|
983 |
j++ |
|
984 |
}
|
|
985 |
if i < j { |
|
986 |
p.comments = comments[i:j] |
|
987 |
}
|
|
988 |
} else if n, ok := node.(*ast.File); ok { |
|
989 |
// use ast.File comments, if any
|
|
990 |
p.comments = n.Comments |
|
991 |
}
|
|
992 |
||
993 |
// if there are no comments, use node comments
|
|
994 |
p.useNodeComments = p.comments == nil |
|
995 |
||
996 |
// get comments ready for use
|
|
997 |
p.nextComment() |
|
998 |
||
999 |
// format node
|
|
1000 |
switch n := node.(type) { |
|
1001 |
case ast.Expr: |
|
1002 |
p.expr(n) |
|
1003 |
case ast.Stmt: |
|
1.2.1
by Michael Stapelberg
Import upstream version 1.1~hg20130304 |
1004 |
// A labeled statement will un-indent to position the label.
|
1005 |
// Set p.indent to 1 so we don't get indent "underflow".
|
|
1006 |
if _, ok := n.(*ast.LabeledStmt); ok { |
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
1007 |
p.indent = 1 |
1008 |
}
|
|
1009 |
p.stmt(n, false) |
|
1010 |
case ast.Decl: |
|
1011 |
p.decl(n) |
|
1012 |
case ast.Spec: |
|
1013 |
p.spec(n, 1, false) |
|
1.2.1
by Michael Stapelberg
Import upstream version 1.1~hg20130304 |
1014 |
case []ast.Stmt: |
1015 |
// A labeled statement will un-indent to position the label.
|
|
1016 |
// Set p.indent to 1 so we don't get indent "underflow".
|
|
1017 |
for _, s := range n { |
|
1018 |
if _, ok := s.(*ast.LabeledStmt); ok { |
|
1019 |
p.indent = 1 |
|
1020 |
}
|
|
1021 |
}
|
|
1022 |
p.stmtList(n, 0, false) |
|
1023 |
case []ast.Decl: |
|
1024 |
p.declList(n) |
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
1025 |
case *ast.File: |
1026 |
p.file(n) |
|
1027 |
default: |
|
1028 |
goto unsupported |
|
1029 |
}
|
|
1030 |
||
1031 |
return nil |
|
1032 |
||
1033 |
unsupported: |
|
1034 |
return fmt.Errorf("go/printer: unsupported node type %T", node) |
|
1035 |
}
|
|
1036 |
||
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
1037 |
// ----------------------------------------------------------------------------
|
1038 |
// Trimmer
|
|
1039 |
||
1040 |
// A trimmer is an io.Writer filter for stripping tabwriter.Escape
|
|
1041 |
// characters, trailing blanks and tabs, and for converting formfeed
|
|
1042 |
// and vtab characters into newlines and htabs (in case no tabwriter
|
|
1043 |
// is used). Text bracketed by tabwriter.Escape characters is passed
|
|
1044 |
// through unchanged.
|
|
1045 |
//
|
|
1046 |
type trimmer struct { |
|
1.1.1
by Ondřej Surý
Import upstream version 2011.04.13 |
1047 |
output io.Writer |
1048 |
state int |
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
1049 |
space []byte |
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
1050 |
}
|
1051 |
||
1052 |
// trimmer is implemented as a state machine.
|
|
1053 |
// It can be in one of the following states:
|
|
1054 |
const ( |
|
1055 |
inSpace = iota // inside space |
|
1056 |
inEscape // inside text bracketed by tabwriter.Escapes |
|
1057 |
inText // inside text |
|
1058 |
)
|
|
1059 |
||
1.1.13
by Ondřej Surý
Import upstream version 1 |
1060 |
func (p *trimmer) resetSpace() { |
1061 |
p.state = inSpace |
|
1062 |
p.space = p.space[0:0] |
|
1063 |
}
|
|
1064 |
||
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
1065 |
// Design note: It is tempting to eliminate extra blanks occurring in
|
1066 |
// whitespace in this function as it could simplify some
|
|
1067 |
// of the blanks logic in the node printing functions.
|
|
1068 |
// However, this would mess up any formatting done by
|
|
1069 |
// the tabwriter.
|
|
1070 |
||
1.1.13
by Ondřej Surý
Import upstream version 1 |
1071 |
var aNewline = []byte("\n") |
1072 |
||
1073 |
func (p *trimmer) Write(data []byte) (n int, err error) { |
|
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
1074 |
// invariants:
|
1.1.1
by Ondřej Surý
Import upstream version 2011.04.13 |
1075 |
// p.state == inSpace:
|
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
1076 |
// p.space is unwritten
|
1077 |
// p.state == inEscape, inText:
|
|
1078 |
// data[m:n] is unwritten
|
|
1079 |
m := 0 |
|
1080 |
var b byte |
|
1081 |
for n, b = range data { |
|
1082 |
if b == '\v' { |
|
1083 |
b = '\t' // convert to htab |
|
1084 |
}
|
|
1085 |
switch p.state { |
|
1086 |
case inSpace: |
|
1087 |
switch b { |
|
1088 |
case '\t', ' ': |
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
1089 |
p.space = append(p.space, b) |
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
1090 |
case '\n', '\f': |
1.1.13
by Ondřej Surý
Import upstream version 1 |
1091 |
p.resetSpace() // discard trailing space |
1092 |
_, err = p.output.Write(aNewline) |
|
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
1093 |
case tabwriter.Escape: |
1.1.13
by Ondřej Surý
Import upstream version 1 |
1094 |
_, err = p.output.Write(p.space) |
1.1.1
by Ondřej Surý
Import upstream version 2011.04.13 |
1095 |
p.state = inEscape |
1096 |
m = n + 1 // +1: skip tabwriter.Escape |
|
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
1097 |
default: |
1.1.13
by Ondřej Surý
Import upstream version 1 |
1098 |
_, err = p.output.Write(p.space) |
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
1099 |
p.state = inText |
1100 |
m = n |
|
1101 |
}
|
|
1102 |
case inEscape: |
|
1103 |
if b == tabwriter.Escape { |
|
1104 |
_, err = p.output.Write(data[m:n]) |
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
1105 |
p.resetSpace() |
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
1106 |
}
|
1107 |
case inText: |
|
1108 |
switch b { |
|
1109 |
case '\t', ' ': |
|
1110 |
_, err = p.output.Write(data[m:n]) |
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
1111 |
p.resetSpace() |
1112 |
p.space = append(p.space, b) |
|
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
1113 |
case '\n', '\f': |
1114 |
_, err = p.output.Write(data[m:n]) |
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
1115 |
p.resetSpace() |
1116 |
_, err = p.output.Write(aNewline) |
|
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
1117 |
case tabwriter.Escape: |
1118 |
_, err = p.output.Write(data[m:n]) |
|
1.1.1
by Ondřej Surý
Import upstream version 2011.04.13 |
1119 |
p.state = inEscape |
1120 |
m = n + 1 // +1: skip tabwriter.Escape |
|
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
1121 |
}
|
1.1.1
by Ondřej Surý
Import upstream version 2011.04.13 |
1122 |
default: |
1123 |
panic("unreachable") |
|
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
1124 |
}
|
1125 |
if err != nil { |
|
1126 |
return
|
|
1127 |
}
|
|
1128 |
}
|
|
1129 |
n = len(data) |
|
1130 |
||
1131 |
switch p.state { |
|
1132 |
case inEscape, inText: |
|
1133 |
_, err = p.output.Write(data[m:n]) |
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
1134 |
p.resetSpace() |
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
1135 |
}
|
1136 |
||
1137 |
return
|
|
1138 |
}
|
|
1139 |
||
1140 |
// ----------------------------------------------------------------------------
|
|
1141 |
// Public interface
|
|
1142 |
||
1.2.1
by Michael Stapelberg
Import upstream version 1.1~hg20130304 |
1143 |
// A Mode value is a set of flags (or 0). They control printing.
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
1144 |
type Mode uint |
1145 |
||
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
1146 |
const ( |
1.1.13
by Ondřej Surý
Import upstream version 1 |
1147 |
RawFormat Mode = 1 << iota // do not use a tabwriter; if set, UseSpaces is ignored |
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
1148 |
TabIndent // use tabs for indentation independent of UseSpaces |
1149 |
UseSpaces // use spaces instead of tabs for alignment |
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
1150 |
SourcePos // emit //line comments to preserve original source positions |
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
1151 |
)
|
1152 |
||
1153 |
// A Config node controls the output of Fprint.
|
|
1154 |
type Config struct { |
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
1155 |
Mode Mode // default: 0 |
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
1156 |
Tabwidth int // default: 8 |
1.2.1
by Michael Stapelberg
Import upstream version 1.1~hg20130304 |
1157 |
Indent int // default: 0 (all code is indented at least by this much) |
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
1158 |
}
|
1159 |
||
1.1.1
by Ondřej Surý
Import upstream version 2011.04.13 |
1160 |
// fprint implements Fprint and takes a nodesSizes map for setting up the printer state.
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
1161 |
func (cfg *Config) fprint(output io.Writer, fset *token.FileSet, node interface{}, nodeSizes map[ast.Node]int) (err error) { |
1162 |
// print node
|
|
1163 |
var p printer |
|
1164 |
p.init(cfg, fset, nodeSizes) |
|
1165 |
if err = p.printNode(node); err != nil { |
|
1166 |
return
|
|
1167 |
}
|
|
1168 |
// print outstanding comments
|
|
1169 |
p.impliedSemi = false // EOF acts like a newline |
|
1170 |
p.flush(token.Position{Offset: infinity, Line: infinity}, token.EOF) |
|
1171 |
||
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
1172 |
// redirect output through a trimmer to eliminate trailing whitespace
|
1173 |
// (Input to a tabwriter must be untrimmed since trailing tabs provide
|
|
1174 |
// formatting information. The tabwriter could provide trimming
|
|
1175 |
// functionality but no tabwriter is used when RawFormat is set.)
|
|
1176 |
output = &trimmer{output: output} |
|
1177 |
||
1.1.13
by Ondřej Surý
Import upstream version 1 |
1178 |
// redirect output through a tabwriter if necessary
|
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
1179 |
if cfg.Mode&RawFormat == 0 { |
1180 |
minwidth := cfg.Tabwidth |
|
1181 |
||
1182 |
padchar := byte('\t') |
|
1183 |
if cfg.Mode&UseSpaces != 0 { |
|
1184 |
padchar = ' ' |
|
1185 |
}
|
|
1186 |
||
1187 |
twmode := tabwriter.DiscardEmptyColumns |
|
1188 |
if cfg.Mode&TabIndent != 0 { |
|
1189 |
minwidth = 0 |
|
1190 |
twmode |= tabwriter.TabIndent |
|
1191 |
}
|
|
1192 |
||
1.1.13
by Ondřej Surý
Import upstream version 1 |
1193 |
output = tabwriter.NewWriter(output, minwidth, cfg.Tabwidth, 1, padchar, twmode) |
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
1194 |
}
|
1195 |
||
1.1.13
by Ondřej Surý
Import upstream version 1 |
1196 |
// write printer result via tabwriter/trimmer to output
|
1197 |
if _, err = output.Write(p.output); err != nil { |
|
1198 |
return
|
|
1199 |
}
|
|
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
1200 |
|
1201 |
// flush tabwriter, if any
|
|
1.2.1
by Michael Stapelberg
Import upstream version 1.1~hg20130304 |
1202 |
if tw, _ := output.(*tabwriter.Writer); tw != nil { |
1.1.13
by Ondřej Surý
Import upstream version 1 |
1203 |
err = tw.Flush() |
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
1204 |
}
|
1205 |
||
1.1.13
by Ondřej Surý
Import upstream version 1 |
1206 |
return
|
1207 |
}
|
|
1208 |
||
1209 |
// A CommentedNode bundles an AST node and corresponding comments.
|
|
1210 |
// It may be provided as argument to any of the Fprint functions.
|
|
1211 |
//
|
|
1212 |
type CommentedNode struct { |
|
1213 |
Node interface{} // *ast.File, or ast.Expr, ast.Decl, ast.Spec, or ast.Stmt |
|
1214 |
Comments []*ast.CommentGroup |
|
1215 |
}
|
|
1216 |
||
1217 |
// Fprint "pretty-prints" an AST node to output for a given configuration cfg.
|
|
1.1.1
by Ondřej Surý
Import upstream version 2011.04.13 |
1218 |
// Position information is interpreted relative to the file set fset.
|
1.2.1
by Michael Stapelberg
Import upstream version 1.1~hg20130304 |
1219 |
// The node type must be *ast.File, *CommentedNode, []ast.Decl, []ast.Stmt,
|
1220 |
// or assignment-compatible to ast.Expr, ast.Decl, ast.Spec, or ast.Stmt.
|
|
1.1.1
by Ondřej Surý
Import upstream version 2011.04.13 |
1221 |
//
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
1222 |
func (cfg *Config) Fprint(output io.Writer, fset *token.FileSet, node interface{}) error { |
1.1.1
by Ondřej Surý
Import upstream version 2011.04.13 |
1223 |
return cfg.fprint(output, fset, node, make(map[ast.Node]int)) |
1224 |
}
|
|
1225 |
||
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
1226 |
// Fprint "pretty-prints" an AST node to output.
|
1227 |
// It calls Config.Fprint with default settings.
|
|
1228 |
//
|
|
1.1.13
by Ondřej Surý
Import upstream version 1 |
1229 |
func Fprint(output io.Writer, fset *token.FileSet, node interface{}) error { |
1230 |
return (&Config{Tabwidth: 8}).Fprint(output, fset, node) |
|
1
by Ondřej Surý
Import upstream version 2011.03.07.1 |
1231 |
}
|