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