~ubuntu-branches/ubuntu/utopic/golang/utopic

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
}