Source file
src/go/printer/printer.go
1
2
3
4
5
6 package printer
7
8 import (
9 "fmt"
10 "go/ast"
11 "go/token"
12 "io"
13 "os"
14 "strconv"
15 "strings"
16 "text/tabwriter"
17 "unicode"
18 )
19
20 const (
21 maxNewlines = 2
22 debug = false
23 infinity = 1 << 30
24 )
25
26 type whiteSpace byte
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
39 type pmode int
40
41 const (
42 noExtraBlank pmode = 1 << iota
43 noExtraLinebreak
44 )
45
46 type commentInfo struct {
47 cindex int
48 comment *ast.CommentGroup
49 commentOffset int
50 commentNewline bool
51 }
52
53 type printer struct {
54
55 Config
56 fset *token.FileSet
57
58
59 output []byte
60 indent int
61 level int
62 mode pmode
63 impliedSemi bool
64 lastTok token.Token
65 prevOpen token.Token
66 wsbuf []whiteSpace
67
68
69
70
71
72
73
74 pos token.Position
75 out token.Position
76 last token.Position
77 linePtr *int
78
79
80 comments []*ast.CommentGroup
81 useNodeComments bool
82
83
84 commentInfo
85
86
87 nodeSizes map[ast.Node]int
88
89
90 cachedPos token.Pos
91 cachedLine int
92 }
93
94 func (p *printer) init(cfg *Config, fset *token.FileSet, nodeSizes map[ast.Node]int) {
95 p.Config = *cfg
96 p.fset = fset
97 p.pos = token.Position{Line: 1, Column: 1}
98 p.out = token.Position{Line: 1, Column: 1}
99 p.wsbuf = make([]whiteSpace, 0, 16)
100 p.nodeSizes = nodeSizes
101 p.cachedPos = -1
102 }
103
104 func (p *printer) internalError(msg ...interface{}) {
105 if debug {
106 fmt.Print(p.pos.String() + ": ")
107 fmt.Println(msg...)
108 panic("go/printer")
109 }
110 }
111
112
113
114
115 func (p *printer) commentsHaveNewline(list []*ast.Comment) bool {
116
117 line := p.lineFor(list[0].Pos())
118 for i, c := range list {
119 if i > 0 && p.lineFor(list[i].Pos()) != line {
120
121 return true
122 }
123 if t := c.Text; len(t) >= 2 && (t[1] == '/' || strings.Contains(t, "\n")) {
124 return true
125 }
126 }
127 _ = line
128 return false
129 }
130
131 func (p *printer) nextComment() {
132 for p.cindex < len(p.comments) {
133 c := p.comments[p.cindex]
134 p.cindex++
135 if list := c.List; len(list) > 0 {
136 p.comment = c
137 p.commentOffset = p.posFor(list[0].Pos()).Offset
138 p.commentNewline = p.commentsHaveNewline(list)
139 return
140 }
141
142
143 }
144
145 p.commentOffset = infinity
146 }
147
148
149
150
151
152 func (p *printer) commentBefore(next token.Position) bool {
153 return p.commentOffset < next.Offset && (!p.impliedSemi || !p.commentNewline)
154 }
155
156
157
158
159 func (p *printer) commentSizeBefore(next token.Position) int {
160
161 defer func(info commentInfo) {
162 p.commentInfo = info
163 }(p.commentInfo)
164
165 size := 0
166 for p.commentBefore(next) {
167 for _, c := range p.comment.List {
168 size += len(c.Text)
169 }
170 p.nextComment()
171 }
172 return size
173 }
174
175
176
177
178
179
180 func (p *printer) recordLine(linePtr *int) {
181 p.linePtr = linePtr
182 }
183
184
185
186
187
188
189 func (p *printer) linesFrom(line int) int {
190 return p.out.Line - line
191 }
192
193 func (p *printer) posFor(pos token.Pos) token.Position {
194
195 return p.fset.Position(pos)
196 }
197
198 func (p *printer) lineFor(pos token.Pos) int {
199 if pos != p.cachedPos {
200 p.cachedPos = pos
201 p.cachedLine = p.fset.Position(pos).Line
202 }
203 return p.cachedLine
204 }
205
206
207 func (p *printer) writeLineDirective(pos token.Position) {
208 if pos.IsValid() && (p.out.Line != pos.Line || p.out.Filename != pos.Filename) {
209 p.output = append(p.output, tabwriter.Escape)
210 p.output = append(p.output, fmt.Sprintf("//line %s:%d\n", pos.Filename, pos.Line)...)
211 p.output = append(p.output, tabwriter.Escape)
212
213 p.out.Filename = pos.Filename
214 p.out.Line = pos.Line
215 }
216 }
217
218
219 func (p *printer) writeIndent() {
220
221
222 n := p.Config.Indent + p.indent
223 for i := 0; i < n; i++ {
224 p.output = append(p.output, '\t')
225 }
226
227
228 p.pos.Offset += n
229 p.pos.Column += n
230 p.out.Column += n
231 }
232
233
234
235 func (p *printer) writeByte(ch byte, n int) {
236 if p.out.Column == 1 {
237
238 p.writeIndent()
239 }
240
241 for i := 0; i < n; i++ {
242 p.output = append(p.output, ch)
243 }
244
245
246 p.pos.Offset += n
247 if ch == '\n' || ch == '\f' {
248 p.pos.Line += n
249 p.out.Line += n
250 p.pos.Column = 1
251 p.out.Column = 1
252 return
253 }
254 p.pos.Column += n
255 p.out.Column += n
256 }
257
258
259
260
261
262
263
264
265
266
267
268
269 func (p *printer) writeString(pos token.Position, s string, isLit bool) {
270 if p.out.Column == 1 {
271 if p.Config.Mode&SourcePos != 0 {
272 p.writeLineDirective(pos)
273 }
274 p.writeIndent()
275 }
276
277 if pos.IsValid() {
278
279
280
281
282 p.pos = pos
283 }
284
285 if isLit {
286
287
288
289
290 p.output = append(p.output, tabwriter.Escape)
291 }
292
293 if debug {
294 p.output = append(p.output, fmt.Sprintf("/*%s*/", pos)...)
295 }
296 p.output = append(p.output, s...)
297
298
299 nlines := 0
300 var li int
301 for i := 0; i < len(s); i++ {
302
303 if s[i] == '\n' {
304 nlines++
305 li = i
306 }
307 }
308 p.pos.Offset += len(s)
309 if nlines > 0 {
310 p.pos.Line += nlines
311 p.out.Line += nlines
312 c := len(s) - li
313 p.pos.Column = c
314 p.out.Column = c
315 } else {
316 p.pos.Column += len(s)
317 p.out.Column += len(s)
318 }
319
320 if isLit {
321 p.output = append(p.output, tabwriter.Escape)
322 }
323
324 p.last = p.pos
325 }
326
327
328
329
330
331
332
333
334 func (p *printer) writeCommentPrefix(pos, next token.Position, prev *ast.Comment, tok token.Token) {
335 if len(p.output) == 0 {
336
337 return
338 }
339
340 if pos.IsValid() && pos.Filename != p.last.Filename {
341
342 p.writeByte('\f', maxNewlines)
343 return
344 }
345
346 if pos.Line == p.last.Line && (prev == nil || prev.Text[1] != '/') {
347
348
349 hasSep := false
350 if prev == nil {
351
352 j := 0
353 for i, ch := range p.wsbuf {
354 switch ch {
355 case blank:
356
357 p.wsbuf[i] = ignore
358 continue
359 case vtab:
360
361
362 hasSep = true
363 continue
364 case indent:
365
366 continue
367 }
368 j = i
369 break
370 }
371 p.writeWhitespace(j)
372 }
373
374 if !hasSep {
375 sep := byte('\t')
376 if pos.Line == next.Line {
377
378
379
380 sep = ' '
381 }
382 p.writeByte(sep, 1)
383 }
384
385 } else {
386
387
388 droppedLinebreak := false
389 j := 0
390 for i, ch := range p.wsbuf {
391 switch ch {
392 case blank, vtab:
393
394 p.wsbuf[i] = ignore
395 continue
396 case indent:
397
398 continue
399 case unindent:
400
401
402
403
404 if i+1 < len(p.wsbuf) && p.wsbuf[i+1] == unindent {
405 continue
406 }
407
408
409
410
411
412
413 if tok != token.RBRACE && pos.Column == next.Column {
414 continue
415 }
416 case newline, formfeed:
417 p.wsbuf[i] = ignore
418 droppedLinebreak = prev == nil
419 }
420 j = i
421 break
422 }
423 p.writeWhitespace(j)
424
425
426 n := 0
427 if pos.IsValid() && p.last.IsValid() {
428 n = pos.Line - p.last.Line
429 if n < 0 {
430 n = 0
431 }
432 }
433
434
435
436
437
438 if p.indent == 0 && droppedLinebreak {
439 n++
440 }
441
442
443
444 if n == 0 && prev != nil && prev.Text[1] == '/' {
445 n = 1
446 }
447
448 if n > 0 {
449
450
451
452 p.writeByte('\f', nlimit(n))
453 }
454 }
455 }
456
457
458
459
460 func isBlank(s string) bool {
461 for i := 0; i < len(s); i++ {
462 if s[i] > ' ' {
463 return false
464 }
465 }
466 return true
467 }
468
469
470 func commonPrefix(a, b string) string {
471 i := 0
472 for i < len(a) && i < len(b) && a[i] == b[i] && (a[i] <= ' ' || a[i] == '*') {
473 i++
474 }
475 return a[0:i]
476 }
477
478
479 func trimRight(s string) string {
480 return strings.TrimRightFunc(s, unicode.IsSpace)
481 }
482
483
484
485
486
487
488
489 func stripCommonPrefix(lines []string) {
490 if len(lines) <= 1 {
491 return
492 }
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514 prefix := ""
515 prefixSet := false
516 if len(lines) > 2 {
517 for i, line := range lines[1 : len(lines)-1] {
518 if isBlank(line) {
519 lines[1+i] = ""
520 } else {
521 if !prefixSet {
522 prefix = line
523 prefixSet = true
524 }
525 prefix = commonPrefix(prefix, line)
526 }
527
528 }
529 }
530
531 if !prefixSet {
532 line := lines[len(lines)-1]
533 prefix = commonPrefix(line, line)
534 }
535
536
539 lineOfStars := false
540 if i := strings.Index(prefix, "*"); i >= 0 {
541
542 if i > 0 && prefix[i-1] == ' ' {
543 i--
544 }
545 prefix = prefix[0:i]
546 lineOfStars = true
547 } else {
548
549
550
551
552
553
554
555 first := lines[0]
556 if isBlank(first[2:]) {
557
558
559
560
561
562 i := len(prefix)
563 for n := 0; n < 3 && i > 0 && prefix[i-1] == ' '; n++ {
564 i--
565 }
566 if i == len(prefix) && i > 0 && prefix[i-1] == '\t' {
567 i--
568 }
569 prefix = prefix[0:i]
570 } else {
571
572 suffix := make([]byte, len(first))
573 n := 2
574 for n < len(first) && first[n] <= ' ' {
575 suffix[n] = first[n]
576 n++
577 }
578 if n > 2 && suffix[2] == '\t' {
579
580 suffix = suffix[2:n]
581 } else {
582
583 suffix[0], suffix[1] = ' ', ' '
584 suffix = suffix[0:n]
585 }
586
587
588 prefix = strings.TrimSuffix(prefix, string(suffix))
589 }
590 }
591
592
593
594
595 last := lines[len(lines)-1]
596 closing := "*/"
597 i := strings.Index(last, closing)
598 if isBlank(last[0:i]) {
599
600 if lineOfStars {
601 closing = " */"
602 }
603 lines[len(lines)-1] = prefix + closing
604 } else {
605
606
607
608 prefix = commonPrefix(prefix, last)
609 }
610
611
612 for i, line := range lines {
613 if i > 0 && line != "" {
614 lines[i] = line[len(prefix):]
615 }
616 }
617 }
618
619 func (p *printer) writeComment(comment *ast.Comment) {
620 text := comment.Text
621 pos := p.posFor(comment.Pos())
622
623 const linePrefix = "//line "
624 if strings.HasPrefix(text, linePrefix) && (!pos.IsValid() || pos.Column == 1) {
625
626 ldir := strings.TrimSpace(text[len(linePrefix):])
627 if i := strings.LastIndex(ldir, ":"); i >= 0 {
628 if line, err := strconv.Atoi(ldir[i+1:]); err == nil && line > 0 {
629
630
631
632
633 indent := p.indent
634 p.indent = 0
635 defer func() {
636 p.pos.Filename = ldir[:i]
637 p.pos.Line = line
638 p.pos.Column = 1
639 p.indent = indent
640 }()
641 }
642 }
643 }
644
645
646 if text[1] == '/' {
647 p.writeString(pos, trimRight(text), true)
648 return
649 }
650
651
652
653 lines := strings.Split(text, "\n")
654
655
656
657
658
659
660
661 if pos.IsValid() && pos.Column == 1 && p.indent > 0 {
662 for i, line := range lines[1:] {
663 lines[1+i] = " " + line
664 }
665 }
666
667 stripCommonPrefix(lines)
668
669
670
671 for i, line := range lines {
672 if i > 0 {
673 p.writeByte('\f', 1)
674 pos = p.pos
675 }
676 if len(line) > 0 {
677 p.writeString(pos, trimRight(line), true)
678 }
679 }
680 }
681
682
683
684
685
686
687
688
689 func (p *printer) writeCommentSuffix(needsLinebreak bool) (wroteNewline, droppedFF bool) {
690 for i, ch := range p.wsbuf {
691 switch ch {
692 case blank, vtab:
693
694 p.wsbuf[i] = ignore
695 case indent, unindent:
696
697 case newline, formfeed:
698
699
700 if needsLinebreak {
701 needsLinebreak = false
702 wroteNewline = true
703 } else {
704 if ch == formfeed {
705 droppedFF = true
706 }
707 p.wsbuf[i] = ignore
708 }
709 }
710 }
711 p.writeWhitespace(len(p.wsbuf))
712
713
714 if needsLinebreak {
715 p.writeByte('\n', 1)
716 wroteNewline = true
717 }
718
719 return
720 }
721
722
723 func (p *printer) containsLinebreak() bool {
724 for _, ch := range p.wsbuf {
725 if ch == newline || ch == formfeed {
726 return true
727 }
728 }
729 return false
730 }
731
732
733
734
735
736
737
738 func (p *printer) intersperseComments(next token.Position, tok token.Token) (wroteNewline, droppedFF bool) {
739 var last *ast.Comment
740 for p.commentBefore(next) {
741 for _, c := range p.comment.List {
742 p.writeCommentPrefix(p.posFor(c.Pos()), next, last, tok)
743 p.writeComment(c)
744 last = c
745 }
746 p.nextComment()
747 }
748
749 if last != nil {
750
751
752
753
754
755
756
757
758
759
760 needsLinebreak := false
761 if p.mode&noExtraBlank == 0 &&
762 last.Text[1] == '*' && p.lineFor(last.Pos()) == next.Line &&
763 tok != token.COMMA &&
764 (tok != token.RPAREN || p.prevOpen == token.LPAREN) &&
765 (tok != token.RBRACK || p.prevOpen == token.LBRACK) {
766 if p.containsLinebreak() && p.mode&noExtraLinebreak == 0 && p.level == 0 {
767 needsLinebreak = true
768 } else {
769 p.writeByte(' ', 1)
770 }
771 }
772
773
774 if last.Text[1] == '/' ||
775 tok == token.EOF ||
776 tok == token.RBRACE && p.mode&noExtraLinebreak == 0 {
777 needsLinebreak = true
778 }
779 return p.writeCommentSuffix(needsLinebreak)
780 }
781
782
783
784 p.internalError("intersperseComments called without pending comments")
785 return
786 }
787
788
789 func (p *printer) writeWhitespace(n int) {
790
791 for i := 0; i < n; i++ {
792 switch ch := p.wsbuf[i]; ch {
793 case ignore:
794
795 case indent:
796 p.indent++
797 case unindent:
798 p.indent--
799 if p.indent < 0 {
800 p.internalError("negative indentation:", p.indent)
801 p.indent = 0
802 }
803 case newline, formfeed:
804
805
806
807
808
809
810 if i+1 < n && p.wsbuf[i+1] == unindent {
811
812
813
814
815
816 p.wsbuf[i], p.wsbuf[i+1] = unindent, formfeed
817 i--
818 continue
819 }
820 fallthrough
821 default:
822 p.writeByte(byte(ch), 1)
823 }
824 }
825
826
827 l := copy(p.wsbuf, p.wsbuf[n:])
828 p.wsbuf = p.wsbuf[:l]
829 }
830
831
832
833
834
835 func nlimit(n int) int {
836 if n > maxNewlines {
837 n = maxNewlines
838 }
839 return n
840 }
841
842 func mayCombine(prev token.Token, next byte) (b bool) {
843 switch prev {
844 case token.INT:
845 b = next == '.'
846 case token.ADD:
847 b = next == '+'
848 case token.SUB:
849 b = next == '-'
850 case token.QUO:
851 b = next == '*'
852 case token.LSS:
853 b = next == '-' || next == '<'
854 case token.AND:
855 b = next == '&' || next == '^'
856 }
857 return
858 }
859
860
861
862
863
864
865
866
867
868
869
870
871 func (p *printer) print(args ...interface{}) {
872 for _, arg := range args {
873
874 var data string
875 var isLit bool
876 var impliedSemi bool
877
878
879 switch p.lastTok {
880 case token.ILLEGAL:
881
882 case token.LPAREN, token.LBRACK:
883 p.prevOpen = p.lastTok
884 default:
885
886 p.prevOpen = token.ILLEGAL
887 }
888
889 switch x := arg.(type) {
890 case pmode:
891
892 p.mode ^= x
893 continue
894
895 case whiteSpace:
896 if x == ignore {
897
898
899
900 continue
901 }
902 i := len(p.wsbuf)
903 if i == cap(p.wsbuf) {
904
905
906
907 p.writeWhitespace(i)
908 i = 0
909 }
910 p.wsbuf = p.wsbuf[0 : i+1]
911 p.wsbuf[i] = x
912 if x == newline || x == formfeed {
913
914
915
916
917 p.impliedSemi = false
918 }
919 p.lastTok = token.ILLEGAL
920 continue
921
922 case *ast.Ident:
923 data = x.Name
924 impliedSemi = true
925 p.lastTok = token.IDENT
926
927 case *ast.BasicLit:
928 data = x.Value
929 isLit = true
930 impliedSemi = true
931 p.lastTok = x.Kind
932
933 case token.Token:
934 s := x.String()
935 if mayCombine(p.lastTok, s[0]) {
936
937
938
939
940
941
942 if len(p.wsbuf) != 0 {
943 p.internalError("whitespace buffer not empty")
944 }
945 p.wsbuf = p.wsbuf[0:1]
946 p.wsbuf[0] = ' '
947 }
948 data = s
949
950 switch x {
951 case token.BREAK, token.CONTINUE, token.FALLTHROUGH, token.RETURN,
952 token.INC, token.DEC, token.RPAREN, token.RBRACK, token.RBRACE:
953 impliedSemi = true
954 }
955 p.lastTok = x
956
957 case token.Pos:
958 if x.IsValid() {
959 p.pos = p.posFor(x)
960 }
961 continue
962
963 case string:
964
965 data = x
966 isLit = true
967 impliedSemi = true
968 p.lastTok = token.STRING
969
970 default:
971 fmt.Fprintf(os.Stderr, "print: unsupported argument %v (%T)\n", arg, arg)
972 panic("go/printer type")
973 }
974
975
976 next := p.pos
977 wroteNewline, droppedFF := p.flush(next, p.lastTok)
978
979
980
981
982 if !p.impliedSemi {
983 n := nlimit(next.Line - p.pos.Line)
984
985 if wroteNewline && n == maxNewlines {
986 n = maxNewlines - 1
987 }
988 if n > 0 {
989 ch := byte('\n')
990 if droppedFF {
991 ch = '\f'
992 }
993 p.writeByte(ch, n)
994 impliedSemi = false
995 }
996 }
997
998
999 if p.linePtr != nil {
1000 *p.linePtr = p.out.Line
1001 p.linePtr = nil
1002 }
1003
1004 p.writeString(next, data, isLit)
1005 p.impliedSemi = impliedSemi
1006 }
1007 }
1008
1009
1010
1011
1012
1013
1014 func (p *printer) flush(next token.Position, tok token.Token) (wroteNewline, droppedFF bool) {
1015 if p.commentBefore(next) {
1016
1017 wroteNewline, droppedFF = p.intersperseComments(next, tok)
1018 } else {
1019
1020 p.writeWhitespace(len(p.wsbuf))
1021 }
1022 return
1023 }
1024
1025
1026 func getDoc(n ast.Node) *ast.CommentGroup {
1027 switch n := n.(type) {
1028 case *ast.Field:
1029 return n.Doc
1030 case *ast.ImportSpec:
1031 return n.Doc
1032 case *ast.ValueSpec:
1033 return n.Doc
1034 case *ast.TypeSpec:
1035 return n.Doc
1036 case *ast.GenDecl:
1037 return n.Doc
1038 case *ast.FuncDecl:
1039 return n.Doc
1040 case *ast.File:
1041 return n.Doc
1042 }
1043 return nil
1044 }
1045
1046 func getLastComment(n ast.Node) *ast.CommentGroup {
1047 switch n := n.(type) {
1048 case *ast.Field:
1049 return n.Comment
1050 case *ast.ImportSpec:
1051 return n.Comment
1052 case *ast.ValueSpec:
1053 return n.Comment
1054 case *ast.TypeSpec:
1055 return n.Comment
1056 case *ast.GenDecl:
1057 if len(n.Specs) > 0 {
1058 return getLastComment(n.Specs[len(n.Specs)-1])
1059 }
1060 case *ast.File:
1061 if len(n.Comments) > 0 {
1062 return n.Comments[len(n.Comments)-1]
1063 }
1064 }
1065 return nil
1066 }
1067
1068 func (p *printer) printNode(node interface{}) error {
1069
1070 var comments []*ast.CommentGroup
1071 if cnode, ok := node.(*CommentedNode); ok {
1072 node = cnode.Node
1073 comments = cnode.Comments
1074 }
1075
1076 if comments != nil {
1077
1078 n, ok := node.(ast.Node)
1079 if !ok {
1080 goto unsupported
1081 }
1082 beg := n.Pos()
1083 end := n.End()
1084
1085
1086
1087
1088 if doc := getDoc(n); doc != nil {
1089 beg = doc.Pos()
1090 }
1091 if com := getLastComment(n); com != nil {
1092 if e := com.End(); e > end {
1093 end = e
1094 }
1095 }
1096
1097
1098 i := 0
1099 for i < len(comments) && comments[i].End() < beg {
1100 i++
1101 }
1102 j := i
1103 for j < len(comments) && comments[j].Pos() < end {
1104 j++
1105 }
1106 if i < j {
1107 p.comments = comments[i:j]
1108 }
1109 } else if n, ok := node.(*ast.File); ok {
1110
1111 p.comments = n.Comments
1112 }
1113
1114
1115 p.useNodeComments = p.comments == nil
1116
1117
1118 p.nextComment()
1119
1120
1121 switch n := node.(type) {
1122 case ast.Expr:
1123 p.expr(n)
1124 case ast.Stmt:
1125
1126
1127 if _, ok := n.(*ast.LabeledStmt); ok {
1128 p.indent = 1
1129 }
1130 p.stmt(n, false)
1131 case ast.Decl:
1132 p.decl(n)
1133 case ast.Spec:
1134 p.spec(n, 1, false)
1135 case []ast.Stmt:
1136
1137
1138 for _, s := range n {
1139 if _, ok := s.(*ast.LabeledStmt); ok {
1140 p.indent = 1
1141 }
1142 }
1143 p.stmtList(n, 0, false)
1144 case []ast.Decl:
1145 p.declList(n)
1146 case *ast.File:
1147 p.file(n)
1148 default:
1149 goto unsupported
1150 }
1151
1152 return nil
1153
1154 unsupported:
1155 return fmt.Errorf("go/printer: unsupported node type %T", node)
1156 }
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167 type trimmer struct {
1168 output io.Writer
1169 state int
1170 space []byte
1171 }
1172
1173
1174
1175 const (
1176 inSpace = iota
1177 inEscape
1178 inText
1179 )
1180
1181 func (p *trimmer) resetSpace() {
1182 p.state = inSpace
1183 p.space = p.space[0:0]
1184 }
1185
1186
1187
1188
1189
1190
1191
1192 var aNewline = []byte("\n")
1193
1194 func (p *trimmer) Write(data []byte) (n int, err error) {
1195
1196
1197
1198
1199
1200 m := 0
1201 var b byte
1202 for n, b = range data {
1203 if b == '\v' {
1204 b = '\t'
1205 }
1206 switch p.state {
1207 case inSpace:
1208 switch b {
1209 case '\t', ' ':
1210 p.space = append(p.space, b)
1211 case '\n', '\f':
1212 p.resetSpace()
1213 _, err = p.output.Write(aNewline)
1214 case tabwriter.Escape:
1215 _, err = p.output.Write(p.space)
1216 p.state = inEscape
1217 m = n + 1
1218 default:
1219 _, err = p.output.Write(p.space)
1220 p.state = inText
1221 m = n
1222 }
1223 case inEscape:
1224 if b == tabwriter.Escape {
1225 _, err = p.output.Write(data[m:n])
1226 p.resetSpace()
1227 }
1228 case inText:
1229 switch b {
1230 case '\t', ' ':
1231 _, err = p.output.Write(data[m:n])
1232 p.resetSpace()
1233 p.space = append(p.space, b)
1234 case '\n', '\f':
1235 _, err = p.output.Write(data[m:n])
1236 p.resetSpace()
1237 if err == nil {
1238 _, err = p.output.Write(aNewline)
1239 }
1240 case tabwriter.Escape:
1241 _, err = p.output.Write(data[m:n])
1242 p.state = inEscape
1243 m = n + 1
1244 }
1245 default:
1246 panic("unreachable")
1247 }
1248 if err != nil {
1249 return
1250 }
1251 }
1252 n = len(data)
1253
1254 switch p.state {
1255 case inEscape, inText:
1256 _, err = p.output.Write(data[m:n])
1257 p.resetSpace()
1258 }
1259
1260 return
1261 }
1262
1263
1264
1265
1266
1267 type Mode uint
1268
1269 const (
1270 RawFormat Mode = 1 << iota
1271 TabIndent
1272 UseSpaces
1273 SourcePos
1274 )
1275
1276
1277 type Config struct {
1278 Mode Mode
1279 Tabwidth int
1280 Indent int
1281 }
1282
1283
1284 func (cfg *Config) fprint(output io.Writer, fset *token.FileSet, node interface{}, nodeSizes map[ast.Node]int) (err error) {
1285
1286 var p printer
1287 p.init(cfg, fset, nodeSizes)
1288 if err = p.printNode(node); err != nil {
1289 return
1290 }
1291
1292 p.impliedSemi = false
1293 p.flush(token.Position{Offset: infinity, Line: infinity}, token.EOF)
1294
1295
1296
1297
1298
1299 output = &trimmer{output: output}
1300
1301
1302 if cfg.Mode&RawFormat == 0 {
1303 minwidth := cfg.Tabwidth
1304
1305 padchar := byte('\t')
1306 if cfg.Mode&UseSpaces != 0 {
1307 padchar = ' '
1308 }
1309
1310 twmode := tabwriter.DiscardEmptyColumns
1311 if cfg.Mode&TabIndent != 0 {
1312 minwidth = 0
1313 twmode |= tabwriter.TabIndent
1314 }
1315
1316 output = tabwriter.NewWriter(output, minwidth, cfg.Tabwidth, 1, padchar, twmode)
1317 }
1318
1319
1320 if _, err = output.Write(p.output); err != nil {
1321 return
1322 }
1323
1324
1325 if tw, _ := output.(*tabwriter.Writer); tw != nil {
1326 err = tw.Flush()
1327 }
1328
1329 return
1330 }
1331
1332
1333
1334
1335 type CommentedNode struct {
1336 Node interface{}
1337 Comments []*ast.CommentGroup
1338 }
1339
1340
1341
1342
1343
1344
1345 func (cfg *Config) Fprint(output io.Writer, fset *token.FileSet, node interface{}) error {
1346 return cfg.fprint(output, fset, node, make(map[ast.Node]int))
1347 }
1348
1349
1350
1351
1352
1353
1354 func Fprint(output io.Writer, fset *token.FileSet, node interface{}) error {
1355 return (&Config{Tabwidth: 8}).Fprint(output, fset, node)
1356 }
1357
View as plain text