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 ...any) {
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 p, _, ok := strings.Cut(prefix, "*"); ok {
563
564 prefix = strings.TrimSuffix(p, " ")
565 lineOfStars = true
566 } else {
567
568
569
570
571
572
573
574 first := lines[0]
575 if isBlank(first[2:]) {
576
577
578
579
580
581 i := len(prefix)
582 for n := 0; n < 3 && i > 0 && prefix[i-1] == ' '; n++ {
583 i--
584 }
585 if i == len(prefix) && i > 0 && prefix[i-1] == '\t' {
586 i--
587 }
588 prefix = prefix[0:i]
589 } else {
590
591 suffix := make([]byte, len(first))
592 n := 2
593 for n < len(first) && first[n] <= ' ' {
594 suffix[n] = first[n]
595 n++
596 }
597 if n > 2 && suffix[2] == '\t' {
598
599 suffix = suffix[2:n]
600 } else {
601
602 suffix[0], suffix[1] = ' ', ' '
603 suffix = suffix[0:n]
604 }
605
606
607 prefix = strings.TrimSuffix(prefix, string(suffix))
608 }
609 }
610
611
612
613
614 last := lines[len(lines)-1]
615 closing := "*/"
616 before, _, _ := strings.Cut(last, closing)
617 if isBlank(before) {
618
619 if lineOfStars {
620 closing = " */"
621 }
622 lines[len(lines)-1] = prefix + closing
623 } else {
624
625
626
627 prefix = commonPrefix(prefix, last)
628 }
629
630
631 for i, line := range lines {
632 if i > 0 && line != "" {
633 lines[i] = line[len(prefix):]
634 }
635 }
636 }
637
638 func (p *printer) writeComment(comment *ast.Comment) {
639 text := comment.Text
640 pos := p.posFor(comment.Pos())
641
642 const linePrefix = "//line "
643 if strings.HasPrefix(text, linePrefix) && (!pos.IsValid() || pos.Column == 1) {
644
645
646 defer func(indent int) { p.indent = indent }(p.indent)
647 p.indent = 0
648 }
649
650
651 if text[1] == '/' {
652 if constraint.IsGoBuild(text) {
653 p.goBuild = append(p.goBuild, len(p.output))
654 } else if constraint.IsPlusBuild(text) {
655 p.plusBuild = append(p.plusBuild, len(p.output))
656 }
657 p.writeString(pos, trimRight(text), true)
658 return
659 }
660
661
662
663 lines := strings.Split(text, "\n")
664
665
666
667
668
669
670
671 if pos.IsValid() && pos.Column == 1 && p.indent > 0 {
672 for i, line := range lines[1:] {
673 lines[1+i] = " " + line
674 }
675 }
676
677 stripCommonPrefix(lines)
678
679
680
681 for i, line := range lines {
682 if i > 0 {
683 p.writeByte('\f', 1)
684 pos = p.pos
685 }
686 if len(line) > 0 {
687 p.writeString(pos, trimRight(line), true)
688 }
689 }
690 }
691
692
693
694
695
696
697
698
699 func (p *printer) writeCommentSuffix(needsLinebreak bool) (wroteNewline, droppedFF bool) {
700 for i, ch := range p.wsbuf {
701 switch ch {
702 case blank, vtab:
703
704 p.wsbuf[i] = ignore
705 case indent, unindent:
706
707 case newline, formfeed:
708
709
710 if needsLinebreak {
711 needsLinebreak = false
712 wroteNewline = true
713 } else {
714 if ch == formfeed {
715 droppedFF = true
716 }
717 p.wsbuf[i] = ignore
718 }
719 }
720 }
721 p.writeWhitespace(len(p.wsbuf))
722
723
724 if needsLinebreak {
725 p.writeByte('\n', 1)
726 wroteNewline = true
727 }
728
729 return
730 }
731
732
733 func (p *printer) containsLinebreak() bool {
734 for _, ch := range p.wsbuf {
735 if ch == newline || ch == formfeed {
736 return true
737 }
738 }
739 return false
740 }
741
742
743
744
745
746
747
748 func (p *printer) intersperseComments(next token.Position, tok token.Token) (wroteNewline, droppedFF bool) {
749 var last *ast.Comment
750 for p.commentBefore(next) {
751 for _, c := range p.comment.List {
752 p.writeCommentPrefix(p.posFor(c.Pos()), next, last, tok)
753 p.writeComment(c)
754 last = c
755 }
756 p.nextComment()
757 }
758
759 if last != nil {
760
761
762
763
764
765
766
767
768
769
770 needsLinebreak := false
771 if p.mode&noExtraBlank == 0 &&
772 last.Text[1] == '*' && p.lineFor(last.Pos()) == next.Line &&
773 tok != token.COMMA &&
774 (tok != token.RPAREN || p.prevOpen == token.LPAREN) &&
775 (tok != token.RBRACK || p.prevOpen == token.LBRACK) {
776 if p.containsLinebreak() && p.mode&noExtraLinebreak == 0 && p.level == 0 {
777 needsLinebreak = true
778 } else {
779 p.writeByte(' ', 1)
780 }
781 }
782
783
784 if last.Text[1] == '/' ||
785 tok == token.EOF ||
786 tok == token.RBRACE && p.mode&noExtraLinebreak == 0 {
787 needsLinebreak = true
788 }
789 return p.writeCommentSuffix(needsLinebreak)
790 }
791
792
793
794 p.internalError("intersperseComments called without pending comments")
795 return
796 }
797
798
799 func (p *printer) writeWhitespace(n int) {
800
801 for i := 0; i < n; i++ {
802 switch ch := p.wsbuf[i]; ch {
803 case ignore:
804
805 case indent:
806 p.indent++
807 case unindent:
808 p.indent--
809 if p.indent < 0 {
810 p.internalError("negative indentation:", p.indent)
811 p.indent = 0
812 }
813 case newline, formfeed:
814
815
816
817
818
819
820 if i+1 < n && p.wsbuf[i+1] == unindent {
821
822
823
824
825
826 p.wsbuf[i], p.wsbuf[i+1] = unindent, formfeed
827 i--
828 continue
829 }
830 fallthrough
831 default:
832 p.writeByte(byte(ch), 1)
833 }
834 }
835
836
837 l := copy(p.wsbuf, p.wsbuf[n:])
838 p.wsbuf = p.wsbuf[:l]
839 }
840
841
842
843
844
845 func nlimit(n int) int {
846 if n > maxNewlines {
847 n = maxNewlines
848 }
849 return n
850 }
851
852 func mayCombine(prev token.Token, next byte) (b bool) {
853 switch prev {
854 case token.INT:
855 b = next == '.'
856 case token.ADD:
857 b = next == '+'
858 case token.SUB:
859 b = next == '-'
860 case token.QUO:
861 b = next == '*'
862 case token.LSS:
863 b = next == '-' || next == '<'
864 case token.AND:
865 b = next == '&' || next == '^'
866 }
867 return
868 }
869
870
871
872
873
874
875
876
877
878
879
880
881 func (p *printer) print(args ...any) {
882 for _, arg := range args {
883
884 var data string
885 var isLit bool
886 var impliedSemi bool
887
888
889 switch p.lastTok {
890 case token.ILLEGAL:
891
892 case token.LPAREN, token.LBRACK:
893 p.prevOpen = p.lastTok
894 default:
895
896 p.prevOpen = token.ILLEGAL
897 }
898
899 switch x := arg.(type) {
900 case pmode:
901
902 p.mode ^= x
903 continue
904
905 case whiteSpace:
906 if x == ignore {
907
908
909
910 continue
911 }
912 i := len(p.wsbuf)
913 if i == cap(p.wsbuf) {
914
915
916
917 p.writeWhitespace(i)
918 i = 0
919 }
920 p.wsbuf = p.wsbuf[0 : i+1]
921 p.wsbuf[i] = x
922 if x == newline || x == formfeed {
923
924
925
926
927 p.impliedSemi = false
928 }
929 p.lastTok = token.ILLEGAL
930 continue
931
932 case *ast.Ident:
933 data = x.Name
934 impliedSemi = true
935 p.lastTok = token.IDENT
936
937 case *ast.BasicLit:
938 data = x.Value
939 isLit = true
940 impliedSemi = true
941 p.lastTok = x.Kind
942
943 case token.Token:
944 s := x.String()
945 if mayCombine(p.lastTok, s[0]) {
946
947
948
949
950
951
952 if len(p.wsbuf) != 0 {
953 p.internalError("whitespace buffer not empty")
954 }
955 p.wsbuf = p.wsbuf[0:1]
956 p.wsbuf[0] = ' '
957 }
958 data = s
959
960 switch x {
961 case token.BREAK, token.CONTINUE, token.FALLTHROUGH, token.RETURN,
962 token.INC, token.DEC, token.RPAREN, token.RBRACK, token.RBRACE:
963 impliedSemi = true
964 }
965 p.lastTok = x
966
967 case token.Pos:
968 if x.IsValid() {
969 p.pos = p.posFor(x)
970 }
971 continue
972
973 case string:
974
975 data = x
976 isLit = true
977 impliedSemi = true
978 p.lastTok = token.STRING
979
980 default:
981 fmt.Fprintf(os.Stderr, "print: unsupported argument %v (%T)\n", arg, arg)
982 panic("go/printer type")
983 }
984
985
986 next := p.pos
987 wroteNewline, droppedFF := p.flush(next, p.lastTok)
988
989
990
991
992 if !p.impliedSemi {
993 n := nlimit(next.Line - p.pos.Line)
994
995 if wroteNewline && n == maxNewlines {
996 n = maxNewlines - 1
997 }
998 if n > 0 {
999 ch := byte('\n')
1000 if droppedFF {
1001 ch = '\f'
1002 }
1003 p.writeByte(ch, n)
1004 impliedSemi = false
1005 }
1006 }
1007
1008
1009 if p.linePtr != nil {
1010 *p.linePtr = p.out.Line
1011 p.linePtr = nil
1012 }
1013
1014 p.writeString(next, data, isLit)
1015 p.impliedSemi = impliedSemi
1016 }
1017 }
1018
1019
1020
1021
1022
1023
1024 func (p *printer) flush(next token.Position, tok token.Token) (wroteNewline, droppedFF bool) {
1025 if p.commentBefore(next) {
1026
1027 wroteNewline, droppedFF = p.intersperseComments(next, tok)
1028 } else {
1029
1030 p.writeWhitespace(len(p.wsbuf))
1031 }
1032 return
1033 }
1034
1035
1036 func getDoc(n ast.Node) *ast.CommentGroup {
1037 switch n := n.(type) {
1038 case *ast.Field:
1039 return n.Doc
1040 case *ast.ImportSpec:
1041 return n.Doc
1042 case *ast.ValueSpec:
1043 return n.Doc
1044 case *ast.TypeSpec:
1045 return n.Doc
1046 case *ast.GenDecl:
1047 return n.Doc
1048 case *ast.FuncDecl:
1049 return n.Doc
1050 case *ast.File:
1051 return n.Doc
1052 }
1053 return nil
1054 }
1055
1056 func getLastComment(n ast.Node) *ast.CommentGroup {
1057 switch n := n.(type) {
1058 case *ast.Field:
1059 return n.Comment
1060 case *ast.ImportSpec:
1061 return n.Comment
1062 case *ast.ValueSpec:
1063 return n.Comment
1064 case *ast.TypeSpec:
1065 return n.Comment
1066 case *ast.GenDecl:
1067 if len(n.Specs) > 0 {
1068 return getLastComment(n.Specs[len(n.Specs)-1])
1069 }
1070 case *ast.File:
1071 if len(n.Comments) > 0 {
1072 return n.Comments[len(n.Comments)-1]
1073 }
1074 }
1075 return nil
1076 }
1077
1078 func (p *printer) printNode(node any) error {
1079
1080 var comments []*ast.CommentGroup
1081 if cnode, ok := node.(*CommentedNode); ok {
1082 node = cnode.Node
1083 comments = cnode.Comments
1084 }
1085
1086 if comments != nil {
1087
1088 n, ok := node.(ast.Node)
1089 if !ok {
1090 goto unsupported
1091 }
1092 beg := n.Pos()
1093 end := n.End()
1094
1095
1096
1097
1098 if doc := getDoc(n); doc != nil {
1099 beg = doc.Pos()
1100 }
1101 if com := getLastComment(n); com != nil {
1102 if e := com.End(); e > end {
1103 end = e
1104 }
1105 }
1106
1107
1108 i := 0
1109 for i < len(comments) && comments[i].End() < beg {
1110 i++
1111 }
1112 j := i
1113 for j < len(comments) && comments[j].Pos() < end {
1114 j++
1115 }
1116 if i < j {
1117 p.comments = comments[i:j]
1118 }
1119 } else if n, ok := node.(*ast.File); ok {
1120
1121 p.comments = n.Comments
1122 }
1123
1124
1125 p.useNodeComments = p.comments == nil
1126
1127
1128 p.nextComment()
1129
1130 p.print(pmode(0))
1131
1132
1133 switch n := node.(type) {
1134 case ast.Expr:
1135 p.expr(n)
1136 case ast.Stmt:
1137
1138
1139 if _, ok := n.(*ast.LabeledStmt); ok {
1140 p.indent = 1
1141 }
1142 p.stmt(n, false)
1143 case ast.Decl:
1144 p.decl(n)
1145 case ast.Spec:
1146 p.spec(n, 1, false)
1147 case []ast.Stmt:
1148
1149
1150 for _, s := range n {
1151 if _, ok := s.(*ast.LabeledStmt); ok {
1152 p.indent = 1
1153 }
1154 }
1155 p.stmtList(n, 0, false)
1156 case []ast.Decl:
1157 p.declList(n)
1158 case *ast.File:
1159 p.file(n)
1160 default:
1161 goto unsupported
1162 }
1163
1164 return nil
1165
1166 unsupported:
1167 return fmt.Errorf("go/printer: unsupported node type %T", node)
1168 }
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179 type trimmer struct {
1180 output io.Writer
1181 state int
1182 space []byte
1183 }
1184
1185
1186
1187 const (
1188 inSpace = iota
1189 inEscape
1190 inText
1191 )
1192
1193 func (p *trimmer) resetSpace() {
1194 p.state = inSpace
1195 p.space = p.space[0:0]
1196 }
1197
1198
1199
1200
1201
1202
1203
1204 var aNewline = []byte("\n")
1205
1206 func (p *trimmer) Write(data []byte) (n int, err error) {
1207
1208
1209
1210
1211
1212 m := 0
1213 var b byte
1214 for n, b = range data {
1215 if b == '\v' {
1216 b = '\t'
1217 }
1218 switch p.state {
1219 case inSpace:
1220 switch b {
1221 case '\t', ' ':
1222 p.space = append(p.space, b)
1223 case '\n', '\f':
1224 p.resetSpace()
1225 _, err = p.output.Write(aNewline)
1226 case tabwriter.Escape:
1227 _, err = p.output.Write(p.space)
1228 p.state = inEscape
1229 m = n + 1
1230 default:
1231 _, err = p.output.Write(p.space)
1232 p.state = inText
1233 m = n
1234 }
1235 case inEscape:
1236 if b == tabwriter.Escape {
1237 _, err = p.output.Write(data[m:n])
1238 p.resetSpace()
1239 }
1240 case inText:
1241 switch b {
1242 case '\t', ' ':
1243 _, err = p.output.Write(data[m:n])
1244 p.resetSpace()
1245 p.space = append(p.space, b)
1246 case '\n', '\f':
1247 _, err = p.output.Write(data[m:n])
1248 p.resetSpace()
1249 if err == nil {
1250 _, err = p.output.Write(aNewline)
1251 }
1252 case tabwriter.Escape:
1253 _, err = p.output.Write(data[m:n])
1254 p.state = inEscape
1255 m = n + 1
1256 }
1257 default:
1258 panic("unreachable")
1259 }
1260 if err != nil {
1261 return
1262 }
1263 }
1264 n = len(data)
1265
1266 switch p.state {
1267 case inEscape, inText:
1268 _, err = p.output.Write(data[m:n])
1269 p.resetSpace()
1270 }
1271
1272 return
1273 }
1274
1275
1276
1277
1278
1279 type Mode uint
1280
1281 const (
1282 RawFormat Mode = 1 << iota
1283 TabIndent
1284 UseSpaces
1285 SourcePos
1286 )
1287
1288
1289
1290
1291
1292
1293 const (
1294
1295
1296
1297
1298
1299
1300
1301 normalizeNumbers Mode = 1 << 30
1302 )
1303
1304
1305 type Config struct {
1306 Mode Mode
1307 Tabwidth int
1308 Indent int
1309 }
1310
1311
1312 func (cfg *Config) fprint(output io.Writer, fset *token.FileSet, node any, nodeSizes map[ast.Node]int) (err error) {
1313
1314 var p printer
1315 p.init(cfg, fset, nodeSizes)
1316 if err = p.printNode(node); err != nil {
1317 return
1318 }
1319
1320 p.impliedSemi = false
1321 p.flush(token.Position{Offset: infinity, Line: infinity}, token.EOF)
1322
1323
1324
1325 p.fixGoBuildLines()
1326
1327
1328
1329
1330
1331 output = &trimmer{output: output}
1332
1333
1334 if cfg.Mode&RawFormat == 0 {
1335 minwidth := cfg.Tabwidth
1336
1337 padchar := byte('\t')
1338 if cfg.Mode&UseSpaces != 0 {
1339 padchar = ' '
1340 }
1341
1342 twmode := tabwriter.DiscardEmptyColumns
1343 if cfg.Mode&TabIndent != 0 {
1344 minwidth = 0
1345 twmode |= tabwriter.TabIndent
1346 }
1347
1348 output = tabwriter.NewWriter(output, minwidth, cfg.Tabwidth, 1, padchar, twmode)
1349 }
1350
1351
1352 if _, err = output.Write(p.output); err != nil {
1353 return
1354 }
1355
1356
1357 if tw, _ := output.(*tabwriter.Writer); tw != nil {
1358 err = tw.Flush()
1359 }
1360
1361 return
1362 }
1363
1364
1365
1366
1367 type CommentedNode struct {
1368 Node any
1369 Comments []*ast.CommentGroup
1370 }
1371
1372
1373
1374
1375
1376
1377 func (cfg *Config) Fprint(output io.Writer, fset *token.FileSet, node any) error {
1378 return cfg.fprint(output, fset, node, make(map[ast.Node]int))
1379 }
1380
1381
1382
1383
1384
1385
1386 func Fprint(output io.Writer, fset *token.FileSet, node any) error {
1387 return (&Config{Tabwidth: 8}).Fprint(output, fset, node)
1388 }
1389
View as plain text