--- /dev/null
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Module file printer.
+
+package modfile
+
+import (
+ "bytes"
+ "fmt"
+ "strings"
+)
+
+// Format returns a go.mod file as a byte slice, formatted in standard style.
+func Format(f *FileSyntax) []byte {
+ pr := &printer{}
+ pr.file(f)
+ return pr.Bytes()
+}
+
+// A printer collects the state during printing of a file or expression.
+type printer struct {
+ bytes.Buffer // output buffer
+ comment []Comment // pending end-of-line comments
+ margin int // left margin (indent), a number of tabs
+}
+
+// printf prints to the buffer.
+func (p *printer) printf(format string, args ...interface{}) {
+ fmt.Fprintf(p, format, args...)
+}
+
+// indent returns the position on the current line, in bytes, 0-indexed.
+func (p *printer) indent() int {
+ b := p.Bytes()
+ n := 0
+ for n < len(b) && b[len(b)-1-n] != '\n' {
+ n++
+ }
+ return n
+}
+
+// newline ends the current line, flushing end-of-line comments.
+func (p *printer) newline() {
+ if len(p.comment) > 0 {
+ p.printf(" ")
+ for i, com := range p.comment {
+ if i > 0 {
+ p.trim()
+ p.printf("\n")
+ for i := 0; i < p.margin; i++ {
+ p.printf("\t")
+ }
+ }
+ p.printf("%s", strings.TrimSpace(com.Token))
+ }
+ p.comment = p.comment[:0]
+ }
+
+ p.trim()
+ p.printf("\n")
+ for i := 0; i < p.margin; i++ {
+ p.printf("\t")
+ }
+}
+
+// trim removes trailing spaces and tabs from the current line.
+func (p *printer) trim() {
+ // Remove trailing spaces and tabs from line we're about to end.
+ b := p.Bytes()
+ n := len(b)
+ for n > 0 && (b[n-1] == '\t' || b[n-1] == ' ') {
+ n--
+ }
+ p.Truncate(n)
+}
+
+// file formats the given file into the print buffer.
+func (p *printer) file(f *FileSyntax) {
+ for _, com := range f.Before {
+ p.printf("%s", strings.TrimSpace(com.Token))
+ p.newline()
+ }
+
+ for i, stmt := range f.Stmt {
+ switch x := stmt.(type) {
+ case *CommentBlock:
+ // comments already handled
+ p.expr(x)
+
+ default:
+ p.expr(x)
+ p.newline()
+ }
+
+ for _, com := range stmt.Comment().After {
+ p.printf("%s", strings.TrimSpace(com.Token))
+ p.newline()
+ }
+
+ if i+1 < len(f.Stmt) {
+ p.newline()
+ }
+ }
+}
+
+func (p *printer) expr(x Expr) {
+ // Emit line-comments preceding this expression.
+ if before := x.Comment().Before; len(before) > 0 {
+ // Want to print a line comment.
+ // Line comments must be at the current margin.
+ p.trim()
+ if p.indent() > 0 {
+ // There's other text on the line. Start a new line.
+ p.printf("\n")
+ }
+ // Re-indent to margin.
+ for i := 0; i < p.margin; i++ {
+ p.printf("\t")
+ }
+ for _, com := range before {
+ p.printf("%s", strings.TrimSpace(com.Token))
+ p.newline()
+ }
+ }
+
+ switch x := x.(type) {
+ default:
+ panic(fmt.Errorf("printer: unexpected type %T", x))
+
+ case *CommentBlock:
+ // done
+
+ case *LParen:
+ p.printf("(")
+ case *RParen:
+ p.printf(")")
+
+ case *Line:
+ p.tokens(x.Token)
+
+ case *LineBlock:
+ p.tokens(x.Token)
+ p.printf(" ")
+ p.expr(&x.LParen)
+ p.margin++
+ for _, l := range x.Line {
+ p.newline()
+ p.expr(l)
+ }
+ p.margin--
+ p.newline()
+ p.expr(&x.RParen)
+ }
+
+ // Queue end-of-line comments for printing when we
+ // reach the end of the line.
+ p.comment = append(p.comment, x.Comment().Suffix...)
+}
+
+func (p *printer) tokens(tokens []string) {
+ sep := ""
+ for _, t := range tokens {
+ if t == "," || t == ")" || t == "]" || t == "}" {
+ sep = ""
+ }
+ p.printf("%s%s", sep, t)
+ sep = " "
+ if t == "(" || t == "[" || t == "{" {
+ sep = ""
+ }
+ }
+}