]> Cypherpunks repositories - gostls13.git/commitdiff
- improved comment intersperse heuristic:
authorRobert Griesemer <gri@golang.org>
Tue, 13 Oct 2009 00:10:50 +0000 (17:10 -0700)
committerRobert Griesemer <gri@golang.org>
Tue, 13 Oct 2009 00:10:50 +0000 (17:10 -0700)
  comments should now be indented properly in corner cases
  (at the end of statement lists, for instance)

- changed import decl. formatting as suggested by Russ (no "global"
  indentation of imports if there are renames present)

- better field list handling

- better documentation

R=rsc
DELTA=534  (324 added, 35 deleted, 175 changed)
OCL=35557
CL=35630

src/pkg/go/printer/printer.go
src/pkg/go/printer/testdata/comments.go
src/pkg/go/printer/testdata/comments.golden
src/pkg/go/printer/testdata/comments.x
src/pkg/go/printer/testdata/declarations.go
src/pkg/go/printer/testdata/declarations.golden
src/pkg/go/printer/testdata/expressions.go
src/pkg/go/printer/testdata/expressions.golden
src/pkg/go/printer/testdata/linebreaks.golden
src/pkg/go/printer/testdata/statements.go
src/pkg/go/printer/testdata/statements.golden

index f6237d4913dd3570afc0bbf55f41610b8841bf78..d2d48f53d486d16dc2e5d6416c1b0e06446fee5c 100644 (file)
@@ -39,14 +39,18 @@ const (
 type whiteSpace int
 
 const (
+       ignore = whiteSpace(0);
        blank = whiteSpace(' ');
        vtab = whiteSpace('\v');
        newline = whiteSpace('\n');
        formfeed = whiteSpace('\f');
+       indent = whiteSpace('>');
+       unindent = whiteSpace('<');
 )
 
 
 var (
+       htab = []byte{'\t'};
        htabs = [...]byte{'\t', '\t', '\t', '\t', '\t', '\t', '\t', '\t'};
        newlines = [...]byte{'\n', '\n', '\n', '\n', '\n', '\n', '\n', '\n'};  // more than maxNewlines
        ampersand = strings.Bytes("&amp;");
@@ -69,25 +73,34 @@ type htmlTag struct {
 
 
 type printer struct {
-       // configuration (does not change after initialization)
+       // Configuration (does not change after initialization)
        output io.Writer;
        mode uint;
        errors chan os.Error;
 
-       // current state (changes during printing)
+       // Current state
        written int;  // number of bytes written
        indent int;  // current indentation
-       last token.Position;  // (possibly estimated) position immediately after the last item; in AST space
-       pos token.Position;  // (possibly estimated) position; in AST space
+
+       // Buffered whitespace
+       buffer []whiteSpace;
+
+       // The (possibly estimated) position in the generated output;
+       // in AST space (i.e., pos is set whenever a token position is
+       // known accurately, and updated dependending on what has been
+       // written)
+       pos token.Position;
+
+       // The value of pos immediately after the last item has been
+       // written using writeItem.
+       last token.Position;
+
+       // HTML support
        tag htmlTag;  // tag to be used around next item
        lastTaggedLine int;  // last line for which a line tag was written
 
-       // buffered whitespace
-       buffer [8]whiteSpace;  // whitespace sequences are short (1 or 2); 8 entries is plenty
-       buflen int;
-
-       // comments
-       comment *ast.CommentGroup;  // list of comments; or nil
+       // The list of comments; or nil.
+       comment *ast.CommentGroup;
 }
 
 
@@ -95,6 +108,7 @@ func (p *printer) init(output io.Writer, mode uint) {
        p.output = output;
        p.mode = mode;
        p.errors = make(chan os.Error);
+       p.buffer = make([]whiteSpace, 0, 16);  // whitespace sequences are short
 }
 
 
@@ -118,35 +132,40 @@ const (
 )
 
 // write interprets data and writes it to p.output. It inserts indentation
-// after newline or formfeed and HTML-escapes characters if GenHTML is set.
+// after newline or formfeed if not in writeRaw mode and HTML-escapes characters
+// if GenHTML is set. It updates p.pos as a side-effect.
 //
 func (p *printer) write(data []byte, mode writeMode) {
        i0 := 0;
        for i, b := range data {
                switch b {
                case '\n', '\f':
-                       if mode & writeRaw == 0 {
-                               // write segment ending in b followed by indentation
-                               p.write0(data[i0 : i+1]);
+                       // write segment ending in b
+                       p.write0(data[i0 : i+1]);
 
+                       // update p.pos
+                       p.pos.Offset += i+1 - i0;
+                       p.pos.Line++;
+                       p.pos.Column = 1;
+
+                       if mode & writeRaw == 0 {
                                // write indentation
-                               // use horizontal ("hard") tabs - indentation columns
+                               // use "hard" htabs - indentation columns
                                // must not be discarded by the tabwriter
                                j := p.indent;
                                for ; j > len(htabs); j -= len(htabs) {
                                        p.write0(&htabs);
                                }
-                               p.write0(htabs[0 : j]);
+                               p.write0(htabs[0:j]);
 
                                // update p.pos
-                               p.pos.Offset += i+1 - i0 + p.indent;
-                               p.pos.Line++;
-                               p.pos.Column = p.indent + 1;
-
-                               // next segment start
-                               i0 = i+1;
+                               p.pos.Offset += p.indent;
+                               p.pos.Column += p.indent;
                        }
 
+                       // next segment start
+                       i0 = i+1;
+
                case '&', '<', '>':
                        if p.mode & GenHTML != 0 {
                                // write segment ending in b
@@ -162,8 +181,9 @@ func (p *printer) write(data []byte, mode writeMode) {
                                p.write0(esc);
 
                                // update p.pos
-                               p.pos.Offset += i+1 - i0;
-                               p.pos.Column += i+1 - i0;
+                               d := i+1 - i0;
+                               p.pos.Offset += d;
+                               p.pos.Column += d;
 
                                // next segment start
                                i0 = i+1;
@@ -175,9 +195,9 @@ func (p *printer) write(data []byte, mode writeMode) {
        p.write0(data[i0 : len(data)]);
 
        // update p.pos
-       n := len(data) - i0;
-       p.pos.Offset += n;
-       p.pos.Column += n;
+       d := len(data) - i0;
+       p.pos.Offset += d;
+       p.pos.Column += d;
 }
 
 
@@ -191,6 +211,13 @@ func (p *printer) writeNewlines(n int) {
 }
 
 
+// writeItem writes data at position pos. data is the text corresponding to
+// a single lexical token, but may also be comment text. pos is the actual
+// (or at least very accurately estimated) position of the data in the original
+// source text. The data may be tagged, depending on p.mode and the mode
+// parameter. writeItem updates p.last to the position immediately following
+// the data.
+//
 func (p *printer) writeItem(pos token.Position, data []byte, mode writeMode) {
        p.pos = pos;
        if debug {
@@ -226,10 +253,81 @@ func (p *printer) writeItem(pos token.Position, data []byte, mode writeMode) {
 }
 
 
-// TODO(gri): decide if this is needed - keep around for now
-/*
-// Reduce contiguous sequences of '\t' in a []byte to a single '\t'.
-func untabify(src []byte) []byte {
+// writeCommentPrefix writes the whitespace before a comment.
+// If there is any pending whitespace, it consumes as much of
+// it as is likely to help the comment position properly.
+// line is the comment line, isFirst indicates if this is the
+// first comment in a group of comments.
+//
+func (p *printer) writeCommentPrefix(line int, isFirst bool) {
+       if !p.last.IsValid() {
+               // there was no preceeding item and the comment is the
+               // first item to be printed - don't write any whitespace
+               return;
+       }
+
+       n := line - p.last.Line;
+       if n == 0 {
+               // comment on the same line as last item:
+               // separate with at least one tab
+               hasTab := false;
+               if isFirst {
+                       j := 0;
+                       for i, ch := range p.buffer {
+                               switch ch {
+                               case blank:
+                                       // ignore any blanks before a comment
+                                       p.buffer[i] = ignore;
+                                       continue;
+                               case vtab:
+                                       // respect existing tabs - important
+                                       // for proper formatting of commented structs
+                                       hasTab = true;
+                                       continue;
+                               case indent:
+                                       // apply pending indentation
+                                       continue;
+                               }
+                               j = i;
+                               break;
+                       }
+                       p.writeWhitespace(j);
+               }
+               // make sure there is at least one tab
+               if !hasTab {
+                       p.write(htab, 0);
+               }
+
+       } else {
+               // comment on a different line:
+               // separate with at least one line break
+               if isFirst {
+                       j := 0;
+                       for i, ch := range p.buffer {
+                               switch ch {
+                               case blank, vtab:
+                                       // ignore any horizontal whitespace before line breaks
+                                       p.buffer[i] = ignore;
+                                       continue;
+                               case indent:
+                                       // apply pending indentation
+                                       continue;
+                               case newline, formfeed:
+                                       // TODO(gri): may want to keep formfeed info in some cases
+                                       p.buffer[i] = ignore;
+                               }
+                               j = i;
+                               break;
+                       }
+                       p.writeWhitespace(j);
+               }
+               p.writeNewlines(n);
+       }
+}
+
+
+// Collapse contiguous sequences of '\t' in a []byte to a single '\t'.
+func collapseTabs(src []byte) []byte {
        dst := make([]byte, len(src));
        j := 0;
        for i, c := range src {
@@ -240,98 +338,128 @@ func untabify(src []byte) []byte {
        }
        return dst[0 : j];
 }
-*/
 
 
 func (p *printer) writeComment(comment *ast.Comment) {
-       // separation from last item
-       if p.last.IsValid() {
-               // there was a preceding item (otherwise, the comment is the
-               // first item to be printed - in that case do not apply extra
-               // spacing)
-               n := comment.Pos().Line - p.last.Line;
-               if n == 0 {
-                       // comment on the same line as last item; separate with tab
-                       p.write(htabs[0 : 1], 0);
-               } else {
-                       // comment on a different line; separate with newlines
-                       p.writeNewlines(n);
-               }
+       // If there are tabs in the comment text, they were probably introduced
+       // to align the comment contents. If the same tab settings were used as
+       // by the printer, reducing tab sequences to single tabs will yield the
+       // original comment again after reformatting via the tabwriter.
+       text := comment.Text;
+       if p.mode & RawFormat == 0 {
+               // tabwriter is used
+               text = collapseTabs(comment.Text);
        }
 
        // write comment
-       p.writeItem(comment.Pos(), comment.Text, 0);
+       p.writeItem(comment.Pos(), text, 0);
 }
 
 
-func (p *printer) intersperseComments(next token.Position) {
-       firstLine := 0;
-       needsNewline := false;
-       for ; p.comment != nil && p.comment.List[0].Pos().Offset < next.Offset; p.comment = p.comment.Next {
-               for _, c := range p.comment.List {
-                       if firstLine == 0 {
-                               firstLine = c.Pos().Line;
+
+// writeCommentSuffix writes a line break after a comment if indicated
+// and processes any leftover indentation information. If a line break
+// is needed, the kind of break (newline vs formfeed) depends on the
+// pending whitespace.
+//
+func (p *printer) writeCommentSuffix(needsLinebreak bool) {
+       for i, ch := range p.buffer {
+               switch ch {
+               case blank, vtab:
+                       // ignore trailing whitespace
+                       p.buffer[i] = ignore;
+               case indent, unindent:
+                       // don't loose indentation information
+               case newline, formfeed:
+                       // if we need a line break, keep exactly one
+                       if needsLinebreak {
+                               needsLinebreak = false;
+                       } else {
+                               p.buffer[i] = ignore;
                        }
-                       p.writeComment(c);
-                       needsNewline = c.Text[1] == '/';
                }
        }
+       p.writeWhitespace(len(p.buffer));
 
-       // Eliminate non-newline whitespace from whitespace buffer.
-       j := 0;
-       for i := 0; i < p.buflen; i++ {
-               ch := p.buffer[i];
-               if ch == '\n' || ch == '\f' {
-                       p.buffer[j] = ch;
-                       j++;
-               }
-       }
-       p.buflen = j;
-
-       // Eliminate extra newlines from whitespace buffer if they
-       // are not present in the original source. This makes sure
-       // that comments that need to be adjacent to a declaration
-       // remain adjacent.
-       if p.last.IsValid() {
-               n := next.Line - p.last.Line;
-               if n < p.buflen {
-                       p.buflen = n;
-               }
+       // make sure we have a line break
+       if needsLinebreak {
+               p.write([]byte{'\n'}, 0);
        }
+}
 
-       // If the whitespace buffer is not empty, it contains only
-       // newline or formfeed chars. Force a formfeed char if the
-       // comments span more than one line - in this case the
-       // structure of the next line is likely to change. Otherwise
-       // use the existing char, if any.
-       if needsNewline {
-               ch := p.buffer[0];  // existing char takes precedence
-               if p.buflen == 0 {
-                       p.buflen = 1;
-                       ch = newline;  // original ch was a lie
-               }
-               if p.last.Line > firstLine {
-                       ch = formfeed;  // comments span at least 2 lines
+
+
+// intersperseComments consumes all comments that appear before the next token
+// and prints it together with the buffered whitespace (i.e., the whitespace
+// that needs to be written before the next token). A heuristic is used to mix
+// the comments and whitespace.
+//
+func (p *printer) intersperseComments(next token.Position) {
+       isFirst := true;
+       needsLinebreak := false;
+       for ; p.commentBefore(next); p.comment = p.comment.Next {
+               for _, c := range p.comment.List {
+                       p.writeCommentPrefix(c.Pos().Line, isFirst);
+                       isFirst = false;
+                       p.writeComment(c);
+                       needsLinebreak = c.Text[1] == '/';
                }
-               p.buffer[0] = ch;
        }
+       p.writeCommentSuffix(needsLinebreak);
 }
 
 
-func (p *printer) writeWhitespace() {
-       var a [len(p.buffer)]byte;
-       for i := 0; i < p.buflen; i++ {
-               a[i] = byte(p.buffer[i]);
+// whiteWhitespace writes the first n whitespace entries.
+func (p *printer) writeWhitespace(n int) {
+       // write entries
+       var data [1]byte;
+       for i := 0; i < n; i++ {
+               switch ch := p.buffer[i]; ch {
+               case ignore:
+                       // ignore!
+               case indent:
+                       p.indent++;
+               case unindent:
+                       p.indent--;
+                       if p.indent < 0 {
+                               // handle gracefully unless in debug mode
+                               if debug {
+                                       panicln("negative indentation:", p.indent);
+                               }
+                               p.indent = 0;
+                       }
+               case newline, formfeed:
+                       // A line break immediately followed by a "correcting"
+                       // unindent is swapped with the unindent - this permits
+                       // proper label positioning. If a comment is between
+                       // the line break and the label, the unindent is not
+                       // part of the comment whitespace prefix and the comment
+                       // will be positioned correctly indented.
+                       if i+1 < n && p.buffer[i+1] == unindent {
+                               p.buffer[i], p.buffer[i+1] = unindent, ch;
+                               i--;  // do it again
+                               continue;
+                       }
+                       fallthrough;
+               default:
+                       data[0] = byte(ch);
+                       p.write(&data, 0);
+               }
        }
 
-       var b []byte = &a;
-       b = b[0 : p.buflen];
-       p.buflen = 0;
-
-       p.write(b, 0);
+       // shift remaining entries down
+       i := 0;
+       for ; n < len(p.buffer); n++ {
+               p.buffer[i] = p.buffer[n];
+               i++;
+       }
+       p.buffer = p.buffer[0:i];
 }
 
 
+// ----------------------------------------------------------------------------
+// Printing interface
+
 // print prints a list of "items" (roughly corresponding to syntactic
 // tokens, but also including whitespace and formatting information).
 // It is the only print function that should be called directly from
@@ -352,21 +480,17 @@ func (p *printer) print(args ...) {
                next := p.pos;  // estimated position of next item
                var data []byte;
                switch x := f.Interface().(type) {
-               case int:
-                       // indentation delta
-                       p.indent += x;
-                       if p.indent < 0 {
-                               panicln("print: negative indentation", p.indent);
-                       }
                case whiteSpace:
-                       if p.buflen >= len(p.buffer) {
+                       i := len(p.buffer);
+                       if i == cap(p.buffer) {
                                // Whitespace sequences are very short so this should
                                // never happen. Handle gracefully (but possibly with
                                // bad comment placement) if it does happen.
-                               p.writeWhitespace();
+                               p.writeWhitespace(i);
+                               i = 0;
                        }
-                       p.buffer[p.buflen] = x;
-                       p.buflen++;
+                       p.buffer = p.buffer[0 : i+1];
+                       p.buffer[i] = x;
                case []byte:
                        data = x;
                        // do not modify multi-line `` strings!
@@ -398,6 +522,8 @@ func (p *printer) print(args ...) {
                        p.flush(next);
 
                        // intersperse extra newlines if present in the source
+                       // (don't do this in flush as it will cause extra newlines
+                       // at the end of a file)
                        p.writeNewlines(next.Line - p.pos.Line);
 
                        p.writeItem(next, data, mode);
@@ -407,16 +533,24 @@ func (p *printer) print(args ...) {
 }
 
 
+// commentBefore returns true iff the current comment occurs
+// before the next position in the source code.
+//
+func (p *printer) commentBefore(next token.Position) bool {
+       return p.comment != nil && p.comment.List[0].Pos().Offset < next.Offset;
+}
+
+
 // Flush prints any pending comments and whitespace occuring
 // textually before the position of the next item.
 //
 func (p *printer) flush(next token.Position) {
        // if there are comments before the next item, intersperse them
-       if p.comment != nil && p.comment.List[0].Pos().Offset < next.Offset {
+       if p.commentBefore(next) {
                p.intersperseComments(next);
        }
-
-       p.writeWhitespace();
+       // write any leftover whitespace
+       p.writeWhitespace(len(p.buffer));
 }
 
 
@@ -426,7 +560,7 @@ func (p *printer) flush(next token.Position) {
 
 // Print as many newlines as necessary (but at least min and and at most
 // max newlines) to get to the current line. If newSection is set, the
-// first newline is printed as a formfeed. Returns true if any linebreak
+// first newline is printed as a formfeed. Returns true if any line break
 // was printed; returns false otherwise.
 //
 // TODO(gri): Reconsider signature (provide position instead of line)
@@ -477,15 +611,13 @@ func (p *printer) leadComment(d *ast.CommentGroup) {
 }
 
 
-// Print n tabs followed by a line comment.
+// Print a tab followed by a line comment.
 // A newline must be printed afterwards since
 // the comment may be a //-style comment.
-func (p *printer) lineComment(n int, d *ast.CommentGroup) {
+func (p *printer) lineComment(d *ast.CommentGroup) {
        // Ignore the comment if we have comments interspersed (p.comment != nil).
        if p.comment == nil && d != nil {
-               for ; n > 0; n-- {
-                       p.print(vtab);
-               }
+               p.print(vtab);
                p.commentList(d.List);
        }
 }
@@ -551,10 +683,10 @@ func (p *printer) exprList(list []ast.Expr, mode exprListMode) {
        // don't add extra indentation if noIndent is set;
        // i.e., pretend that the first line is already indented
        indented := mode&noIndent != 0;
-       // there may or may not be a linebreak before the first list
-       // element; in any case indent once after the first linebreak
+       // there may or may not be a line break before the first list
+       // element; in any case indent once after the first line break
        if p.linebreak(line, 0, 2, true) && !indented {
-               p.print(+1);
+               p.print(htab, indent);  // indent applies to next line
                indented = true;
        }
        for i, x := range list {
@@ -565,10 +697,10 @@ func (p *printer) exprList(list []ast.Expr, mode exprListMode) {
                                p.print(token.COMMA);
                        }
                        if prev < line {
-                               // at least one linebreak, but respect an extra empty line
+                               // at least one line break, but respect an extra empty line
                                // in the source
                                if p.linebreak(x.Pos().Line, 1, 2, true) && !indented {
-                                       p.print(+1);
+                                       p.print(htab, indent);  // indent applies to next line
                                        indented = true;
                                }
                        } else {
@@ -582,11 +714,11 @@ func (p *printer) exprList(list []ast.Expr, mode exprListMode) {
                if indented && mode&noIndent == 0 {
                        // should always be indented here since we have a multi-line
                        // expression list - be conservative and check anyway
-                       p.print(-1);
+                       p.print(unindent);
                }
                p.print(formfeed);  // terminating comma needs a line break to look good
        } else if indented && mode&noIndent == 0 {
-               p.print(-1);
+               p.print(unindent);
        }
 }
 
@@ -598,9 +730,8 @@ func (p *printer) parameters(list []*ast.Field) {
                        if i > 0 {
                                p.print(token.COMMA, blank);
                        }
-                       p.identList(par.Names);
                        if len(par.Names) > 0 {
-                               // at least one identifier
+                               p.identList(par.Names);
                                p.print(blank);
                        }
                        p.expr(par.Type);
@@ -632,33 +763,47 @@ func (p *printer) signature(params, result []*ast.Field) (optSemi bool) {
 
 
 func (p *printer) fieldList(lbrace token.Position, list []*ast.Field, rbrace token.Position, isIncomplete, isStruct bool) {
-       if len(list) == 0 && !isIncomplete {
+       if len(list) == 0 && !isIncomplete && !p.commentBefore(rbrace) {
                // no blank between keyword and {} in this case
-               // TODO(gri): This will not look nice if there are comments inside the {}'s.
                p.print(lbrace, token.LBRACE, rbrace, token.RBRACE);
                return;
        }
 
        // at least one entry or incomplete
-       p.print(blank, lbrace, token.LBRACE, +1, formfeed);
+       p.print(blank, lbrace, token.LBRACE, indent, formfeed);
        if isStruct {
-               sep := blank;
-               if len(list) > 1 {
-                       sep = vtab;
+
+               sep := vtab;
+               if len(list) == 1 {
+                       sep = blank;
                }
                for i, f := range list {
+                       extraTabs := 0;
                        p.leadComment(f.Doc);
                        if len(f.Names) > 0 {
                                p.identList(f.Names);
                                p.print(sep);
+                               p.expr(f.Type);
+                               extraTabs = 1;
+                       } else {
+                               p.expr(f.Type);
+                               extraTabs = 2;
                        }
-                       p.expr(f.Type);
                        if f.Tag != nil {
+                               if len(f.Names) > 0 && sep == vtab {
+                                       p.print(sep);
+                               }
                                p.print(sep);
                                p.expr(&ast.StringList{f.Tag});
+                               extraTabs = 0;
                        }
                        p.print(token.SEMICOLON);
-                       p.lineComment(1, f.Comment);
+                       if f.Comment != nil {
+                               for ; extraTabs > 0; extraTabs-- {
+                                       p.print(vtab);
+                               }
+                               p.lineComment(f.Comment);
+                       }
                        if i+1 < len(list) || isIncomplete {
                                p.print(newline);
                        }
@@ -666,7 +811,9 @@ func (p *printer) fieldList(lbrace token.Position, list []*ast.Field, rbrace tok
                if isIncomplete {
                        p.print("// contains unexported fields");
                }
+
        } else { // interface
+
                for i, f := range list {
                        p.leadComment(f.Doc);
                        if ftyp, isFtyp := f.Type.(*ast.FuncType); isFtyp {
@@ -678,7 +825,7 @@ func (p *printer) fieldList(lbrace token.Position, list []*ast.Field, rbrace tok
                                p.expr(f.Type);
                        }
                        p.print(token.SEMICOLON);
-                       p.lineComment(1, f.Comment);
+                       p.lineComment(f.Comment);
                        if i+1 < len(list) || isIncomplete {
                                p.print(newline);
                        }
@@ -686,8 +833,9 @@ func (p *printer) fieldList(lbrace token.Position, list []*ast.Field, rbrace tok
                if isIncomplete {
                        p.print("// contains unexported methods");
                }
+
        }
-       p.print(-1, formfeed, rbrace, token.RBRACE);
+       p.print(unindent, formfeed, rbrace, token.RBRACE);
 }
 
 
@@ -767,10 +915,10 @@ func (p *printer) binaryExpr(x *ast.BinaryExpr, prec1 int) {
                if printBlanks {
                        if prev != line {
                                p.print(blank, x.OpPos, x.Op);
-                               // at least one linebreak, but respect an extra empty line
+                               // at least one line break, but respect an extra empty line
                                // in the source
                                if p.linebreak(line, 1, 2, false) && !indented {
-                                       p.print(+1);
+                                       p.print(htab, indent);  // indent applies to next line
                                        indented = true;
                                }
                        } else {
@@ -785,7 +933,7 @@ func (p *printer) binaryExpr(x *ast.BinaryExpr, prec1 int) {
                p.expr1(x.Y, prec);
        }
        if indented {
-               p.print(-1);
+               p.print(unindent);
        }
 }
 
@@ -838,7 +986,7 @@ func (p *printer) expr1(expr ast.Expr, prec1 int) (optSemi bool) {
        case *ast.FuncLit:
                p.expr(x.Type);
                p.print(blank);
-               p.stmt(x.Body);
+               p.block(x.Body, 1);
 
        case *ast.ParenExpr:
                p.print(token.LPAREN);
@@ -953,23 +1101,28 @@ const maxStmtNewlines = 2  // maximum number of newlines between statements
 // Print the statement list indented, but without a newline after the last statement.
 // Extra line breaks between statements in the source are respected but at most one
 // empty line is printed between statements.
-func (p *printer) stmtList(list []ast.Stmt, indent int) {
-       p.print(+indent);
+func (p *printer) stmtList(list []ast.Stmt, _indent int) {
+       // TODO(gri): fix _indent code
+       if _indent > 0 {
+               p.print(indent);
+       }
        for i, s := range list {
-               // indent == 0 only for lists of switch/select case clauses;
+               // _indent == 0 only for lists of switch/select case clauses;
                // in those cases each clause is a new section
-               p.linebreak(s.Pos().Line, 1, maxStmtNewlines, i == 0 || indent == 0);
+               p.linebreak(s.Pos().Line, 1, maxStmtNewlines, i == 0 || _indent == 0);
                if !p.stmt(s) {
                        p.print(token.SEMICOLON);
                }
        }
-       p.print(-indent);
+       if _indent > 0 {
+               p.print(unindent);
+       }
 }
 
 
 func (p *printer) block(s *ast.BlockStmt, indent int) {
        p.print(s.Pos(), token.LBRACE);
-       if len(s.List) > 0 {
+       if len(s.List) > 0 || p.commentBefore(s.Rbrace) {
                p.stmtList(s.List, indent);
                p.linebreak(s.Rbrace.Line, 1, maxStmtNewlines, true);
        }
@@ -1039,11 +1192,12 @@ func (p *printer) stmt(stmt ast.Stmt) (optSemi bool) {
                // nothing to do
 
        case *ast.LabeledStmt:
-               // whitespace printing is delayed, thus indentation adjustments
-               // take place before the previous newline/formfeed is printed
-               p.print(-1);
+               // a "correcting" unindent immediately following a line break
+               // is applied before the line break  if there is no comment
+               // between (see writeWhitespace)
+               p.print(unindent);
                p.expr(s.Label);
-               p.print(token.COLON, vtab, +1);
+               p.print(token.COLON, vtab, indent);
                p.linebreak(s.Stmt.Pos().Line, 0, 1, true);
                optSemi = p.stmt(s.Stmt);
 
@@ -1095,9 +1249,9 @@ func (p *printer) stmt(stmt ast.Stmt) (optSemi bool) {
                        case *ast.BlockStmt, *ast.IfStmt:
                                optSemi = p.stmt(s.Else);
                        default:
-                               p.print(token.LBRACE, +1, formfeed);
+                               p.print(token.LBRACE, indent, formfeed);
                                p.stmt(s.Else);
-                               p.print(-1, formfeed, token.RBRACE);
+                               p.print(unindent, formfeed, token.RBRACE);
                        }
                }
 
@@ -1207,29 +1361,22 @@ func (p *printer) spec(spec ast.Spec, n int, context declContext) {
        var (
                optSemi bool;  // true if a semicolon is optional
                comment *ast.CommentGroup;  // a line comment, if any
-               columns int;  // number of (discardable) columns missing before comment, if any
+               extraTabs int;  // number of extra tabs before comment, if any
        )
 
        switch s := spec.(type) {
        case *ast.ImportSpec:
                p.leadComment(s.Doc);
-               if n == 1 {
-                       if s.Name != nil {
-                               p.expr(s.Name);
-                               p.print(blank);
-                       }
-               } else {
-                       if s.Name != nil {
-                               p.expr(s.Name);
-                       }
-                       p.print(vtab);  // column discarded if empty
+               if s.Name != nil {
+                       p.expr(s.Name);
+                       p.print(blank);
                }
                p.expr(&ast.StringList{s.Path});
                comment = s.Comment;
 
        case *ast.ValueSpec:
                p.leadComment(s.Doc);
-               p.identList(s.Names);  // never empty
+               p.identList(s.Names);  // always present
                if n == 1 {
                        if s.Type != nil {
                                p.print(blank);
@@ -1241,20 +1388,20 @@ func (p *printer) spec(spec ast.Spec, n int, context declContext) {
                                optSemi = false;
                        }
                } else {
-                       columns = 2;
+                       extraTabs = 2;
                        if s.Type != nil || s.Values != nil {
                                p.print(vtab);
                        }
                        if s.Type != nil {
                                optSemi = p.expr(s.Type);
-                               columns = 1;
+                               extraTabs = 1;
                        }
                        if s.Values != nil {
                                p.print(vtab);
                                p.print(token.ASSIGN);
                                p.exprList(s.Values, blankStart | commaSep);
                                optSemi = false;
-                               columns = 0;
+                               extraTabs = 0;
                        }
                }
                comment = s.Comment;
@@ -1278,7 +1425,12 @@ func (p *printer) spec(spec ast.Spec, n int, context declContext) {
                p.print(token.SEMICOLON);
        }
 
-       p.lineComment(1+columns, comment);
+       if comment != nil {
+               for ; extraTabs > 0; extraTabs-- {
+                       p.print(vtab);
+               }
+               p.lineComment(comment);
+       }
 }
 
 
@@ -1295,14 +1447,14 @@ func (p *printer) decl(decl ast.Decl, context declContext) {
                        // group of parenthesized declarations
                        p.print(d.Lparen, token.LPAREN);
                        if len(d.Specs) > 0 {
-                               p.print(+1, formfeed);
+                               p.print(indent, formfeed);
                                for i, s := range d.Specs {
                                        if i > 0 {
                                                p.print(newline);
                                        }
                                        p.spec(s, len(d.Specs), inGroup);
                                }
-                               p.print(-1, formfeed);
+                               p.print(unindent, formfeed);
                        }
                        p.print(d.Rparen, token.RPAREN);
 
@@ -1328,7 +1480,7 @@ func (p *printer) decl(decl ast.Decl, context declContext) {
                p.signature(d.Type.Params, d.Type.Results);
                if d.Body != nil {
                        p.print(blank);
-                       p.stmt(d.Body);
+                       p.block(d.Body, 1);
                }
 
        default:
index 271886d7206acc576786d34a10b4cd8790aae420..3fbf84c4e571dadec1582c307ef4a24b3aca0510 100644 (file)
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-// This is a package for testing purposes.
+// This is a package for testing comment placement by go/printer.
 //
 package main
 
@@ -53,7 +53,7 @@ type I1 interface {
 }
 
 // The I2 interface; all methods are exported.
-type I1 interface {
+type I2 interface {
        I0;
        F(x float) float;  // exported method
        G(x float) float;  // exported method
@@ -91,6 +91,11 @@ func f1() {
 }
 
 
+func _() {
+       // this comment should be properly indented
+}
+
+
 func abs(x int) int {
        if x < 0 {  // the tab printed before this comment's // must not affect the remaining lines
                return -x;  // this statement should be properly indented
@@ -114,9 +119,26 @@ func typeswitch(x interface{}) {
 
        switch v0, ok := x.(int); x.(type) {
        case bool, int, float:
+               // this comment should be indented
        case string:
        default:
+               // this comment should be indented
        }
+       // this comment should be indented
 }
 
+
+// Line comments with tabs
+func _() {
+var    finput          *bufio.Reader;                  // input file
+var    stderr          *bufio.Writer;
+var    ftable          *bufio.Writer;                  // y.go file
+var    foutput         *bufio.Writer;                  // y.output file
+
+var    oflag           string;                         // -o [y.go]            - y.go file
+var    vflag           string;                         // -v [y.output]        - y.output file
+var    lflag           bool;                           // -l                   - disable line directives
+}
+
+
 // This comment is the last entry in this file. It must be printed.
index 10fce681cf57d8fb615e855747654a62d4448fa2..948e0dce4bc98a5ded967e8ac56ec6c11efe4066 100644 (file)
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-// This is a package for testing purposes.
+// This is a package for testing comment placement by go/printer.
 //
 package main
 
@@ -53,7 +53,7 @@ type I1 interface {
 }
 
 // The I2 interface; all methods are exported.
-type I1 interface {
+type I2 interface {
        I0;
        F(x float) float;       // exported method
        G(x float) float;       // exported method
@@ -91,6 +91,11 @@ func f1() {
 }
 
 
+func _() {
+       // this comment should be properly indented
+}
+
+
 func abs(x int) int {
        if x < 0 {      // the tab printed before this comment's // must not affect the remaining lines
                return -x;      // this statement should be properly indented
@@ -112,9 +117,26 @@ func typeswitch(x interface{}) {
 
        switch v0, ok := x.(int); x.(type) {
        case bool, int, float:
+               // this comment should be indented
        case string:
        default:
+               // this comment should be indented
        }
+       // this comment should be indented
 }
 
+
+// Line comments with tabs
+func _() {
+       var finput *bufio.Reader;       // input file
+       var stderr *bufio.Writer;
+       var ftable *bufio.Writer;       // y.go file
+       var foutput *bufio.Writer;      // y.output file
+
+       var oflag string;       // -o [y.go]            - y.go file
+       var vflag string;       // -v [y.output]        - y.output file
+       var lflag bool;         // -l                   - disable line directives
+}
+
+
 // This comment is the last entry in this file. It must be printed.
index 79889fae18c868bf6550958437758d823b12cabf..72ac8f0eb82fb355da0cfa201ee6a1743e0e3a61 100644 (file)
@@ -1,4 +1,4 @@
-// This is a package for testing purposes.
+// This is a package for testing comment placement by go/printer.
 //
 package main
 
@@ -48,7 +48,7 @@ type I1 interface {
 
 
 // The I2 interface; all methods are exported.
-type I1 interface {
+type I2 interface {
        I0;
        F(x float) float;
        G(x float) float;
index 6927ac78877a51ae9e6e2443addaba592dd80c13..f301906aa0e61fe22008af19e9715fbc49c46640 100644 (file)
@@ -175,6 +175,14 @@ func _() {
                y = 20;  // comment
                f, ff, fff, ffff int = 0, 1, 2, 3;  // comment
        )
+       // respect original line breaks
+       var _ = []T {
+               T{0x20, "Telugu"}
+       };
+       var _ = []T {
+               // respect original line breaks
+               T{0x20, "Telugu"}
+       };
 }
 
 func _() {
@@ -194,7 +202,13 @@ func _() {
 
 
 // formatting of structs
-type ES struct{}
+type _ struct{}
+
+type _ struct{ /* this comment should be visible */ }
+
+type _ struct{
+       // this comment should be visible and properly indented
+}
 
 type _ struct {  // this comment must not change indentation
        f int;
index 5135f0eb091e3d7eac5e679c0c98de46849e3d11..3c3c636b435d41ff866311e9065eec1982eea40e 100644 (file)
@@ -19,10 +19,10 @@ import (
 )
 
 import (
-                       "io";
-       aLongRename     "io";
-       b               "io";
-       c               "i" "o";
+       "io";
+       aLongRename "io";
+       b "io";
+       c "i" "o";
 )
 
 // no newlines between consecutive single imports, but
@@ -173,6 +173,11 @@ func _() {
                y                               = 20;           // comment
                f, ff, fff, ffff        int     = 0, 1, 2, 3;   // comment
        )
+       // respect original line breaks
+       var _ = []T{T{0x20, "Telugu"}};
+       var _ = []T{
+       // respect original line breaks
+       T{0x20, "Telugu"}};
 }
 
 func _() {
@@ -192,7 +197,13 @@ func _() {
 
 
 // formatting of structs
-type ES struct{}
+type _ struct{}
+
+type _ struct {        /* this comment should be visible */}
+
+type _ struct {
+       // this comment should be visible and properly indented
+}
 
 type _ struct {        // this comment must not change indentation
        f                       int;
@@ -235,7 +246,7 @@ type _ struct {
        bool;
        a, b, c                 int;
        int                     "tag";
-       ES;                     // comment
+       ES;                             // comment
        float                   "tag";  // comment
        f                       int;    // comment
        f, ff, fff, ffff        int;    // comment
@@ -246,7 +257,7 @@ type _ struct {
 
 // difficult cases
 type _ struct {
-       bool;   // comment
+       bool;           // comment
        text    []byte; // comment
 }
 
index 2aca7e8dc01d5fc082d9ae8bb0a1f8251039d68e..29619f496d54ea283aea2379767df8e0df979297 100644 (file)
@@ -165,3 +165,14 @@ func (p *parser) charClass() {
                p.re.add(nl);
        }
 }
+
+
+func addState(s []state, inst instr, match []int) {
+       // handle comments correctly in multi-line expressions
+       for i := 0; i < l; i++ {
+               if s[i].inst.index() == index && // same instruction
+                  s[i].match[0] < pos {        // earlier match already going; leftmost wins
+                       return s
+                }
+       }
+}
index 35d379f36c459f58fadf12b85818ba6efa8db2d2..259a5c31f418f5390259eed42560c205359e7464 100644 (file)
@@ -108,11 +108,9 @@ func _() {
        // do not modify `` strings
        _ = ``;
        _ = `
-`;
-       // TODO(gri): fix line breaks here
+`;     // TODO(gri): fix line breaks here
        _ = `foo
                bar`;
-
 }
 
 
@@ -140,8 +138,8 @@ func _() {
                b < a;
        _ = "1234567890"
        "1234567890";
-// TODO(gri): add more test cases
-// TODO(gri): these comments should be indented
+       // TODO(gri): add more test cases
+       // TODO(gri): these comments should be indented
 }
 
 
@@ -167,3 +165,15 @@ func (p *parser) charClass() {
                p.re.add(nl);
        }
 }
+
+
+func addState(s []state, inst instr, match []int) {
+       // handle comments correctly in multi-line expressions
+       for i := 0; i < l; i++ {
+               if s[i].inst.index() == index &&
+                                               // same instruction
+                       s[i].match[0] < pos {   // earlier match already going; leftmost wins
+                       return s;
+               }
+       }
+}
index 17930114216c70926a3cf0aa80498afb9a787d32..aa4bcb1bfbd1f6f9b500e6aceee43d1a788765eb 100644 (file)
@@ -181,7 +181,9 @@ var facts = map[int]string{
 
 func usage() {
        fmt.Fprintf(os.Stderr,
-               // TODO(gri): the 2nd string of this string list should not be indented
+
+
+       // TODO(gri): the 2nd string of this string list should not be indented
                "usage: godoc package [name ...]\n"
                "       godoc -http=:6060\n");
        flag.PrintDefaults();
index 5c38a4ac458076881ab5ca2d8440b67cf9e76832..85cb5989ce97f06900381c3a1a75e97c2b22f2da 100644 (file)
@@ -123,6 +123,18 @@ func _() {
 
 
 func _() {
+       // this comment should be indented
+       L:
+}
+
+
+func _() {
+       L: _ = 0;
+}
+
+
+func _() {
+       // this comment should be indented
        L: _ = 0;
 }
 
@@ -134,3 +146,13 @@ func _() {
                _ = 0;
        }
 }
+
+
+func _() {
+               // this comment should be indented
+       for {
+       L1: _ = 0;
+       L2:
+               _ = 0;
+       }
+}
index 5826c4abca17d631d4001d39c8d1fd36d071484c..0672ec4223ba3bc6c0eeb6bac2d9a0c7fa0722e5 100644 (file)
@@ -139,6 +139,19 @@ L:
 
 
 func _() {
+       // this comment should be indented
+L:
+       ;
+}
+
+
+func _() {
+L:     _ = 0;
+}
+
+
+func _() {
+       // this comment should be indented
 L:     _ = 0;
 }
 
@@ -150,3 +163,13 @@ func _() {
                _ = 0;
        }
 }
+
+
+func _() {
+       // this comment should be indented
+       for {
+       L1:     _ = 0;
+       L2:
+               _ = 0;
+       }
+}