var noPos token.Position
+// Use ignoreMultiLine if the multiLine information is not important.
+var ignoreMultiLine = new(bool);
+
+
type printer struct {
// Configuration (does not change after initialization)
output io.Writer;
// 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.
+// pos is the comment position, next the position of the item
+// after all pending comments, isFirst indicates if this is the
+// first comment in a group of comments, and isKeyword indicates
+// if the next item is a keyword.
//
-func (p *printer) writeCommentPrefix(line int, isFirst bool) {
+func (p *printer) writeCommentPrefix(pos, next token.Position, isFirst, isKeyword 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;
+ n := pos.Line - p.last.Line;
if n == 0 {
// comment on the same line as last item:
// separate with at least one tab
case indent:
// apply pending indentation
continue;
+ case unindent:
+ // if the next token is a keyword, apply the outdent
+ // if it appears that the comment is aligned with the
+ // keyword; otherwise assume the outdent is part of a
+ // closing block and stop (this scenario appears with
+ // comments before a case label where the comments
+ // apply to the next case instead of the current one)
+ if isKeyword && pos.Column == next.Column {
+ continue;
+ }
case newline, formfeed:
// TODO(gri): may want to keep formfeed info in some cases
p.buffer[i] = ignore;
}
-// 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 {
- if c != '\t' || i == 0 || src[i-1] != '\t' {
- dst[j] = c;
- j++;
+func (p *printer) writeCommentLine(comment *ast.Comment, pos token.Position, line []byte) {
+ // line must pass through unchanged, bracket it with tabwriter.Escape
+ esc := []byte{tabwriter.Escape};
+ line = bytes.Join([][]byte{esc, line, esc}, nil);
+
+ // apply styler, if any
+ var tag HtmlTag;
+ if p.Styler != nil {
+ line, tag = p.Styler.Comment(comment, line);
+ }
+
+ p.writeItem(pos, line, tag);
+}
+
+
+// TODO(gri): Similar (but not quite identical) functionality for
+// comment processing can be found in go/doc/comment.go.
+// Perhaps this can be factored eventually.
+
+// Split comment text into lines
+func split(text []byte) [][]byte {
+ // count lines (comment text never ends in a newline)
+ n := 1;
+ for _, c := range text {
+ if c == '\n' {
+ n++;
+ }
+ }
+
+ // split
+ lines := make([][]byte, n);
+ n = 0;
+ i := 0;
+ for j, c := range text {
+ if c == '\n' {
+ lines[n] = text[i:j]; // exclude newline
+ i = j+1; // discard newline
+ n++;
+ }
+ }
+ lines[n] = text[i:len(text)];
+
+ return lines;
+}
+
+
+func isBlank(s []byte) bool {
+ for _, b := range s {
+ if b > ' ' {
+ return false;
+ }
+ }
+ return true;
+}
+
+
+func commonPrefix(a, b []byte) []byte {
+ i := 0;
+ for i < len(a) && i < len(b) && a[i] == b[i] && (a[i] <= ' ' || a[i] == '*') {
+ i++;
+ }
+ return a[0 : i];
+}
+
+
+func stripCommonPrefix(lines [][]byte) {
+ if len(lines) < 2 {
+ return; // at most one line - nothing to do
+ }
+
+ // The heuristic in this function tries to handle a few
+ // common patterns of /*-style comments: Comments where
+ // the opening /* and closing */ are aligned and the
+ // rest of the comment text is aligned and indented with
+ // blanks or tabs, cases with a vertical "line of stars"
+ // on the left, and cases where the closing */ is on the
+ // same line as the last comment text.
+
+ // Compute maximum common white prefix of all but the first,
+ // last, and blank lines, and replace blank lines with empty
+ // lines (the first line starts with /* and has no prefix).
+ var prefix []byte;
+ for i, line := range lines {
+ switch {
+ case i == 0 || i == len(lines)-1:
+ // ignore
+ case isBlank(line):
+ lines[i] = nil;
+ case prefix == nil:
+ prefix = commonPrefix(line, line);
+ default:
+ prefix = commonPrefix(prefix, line);
+ }
+ }
+
+ /*
+ * Check for vertical "line of stars" and correct prefix accordingly.
+ */
+ lineOfStars := false;
+ if i := bytes.Index(prefix, []byte{'*'}); i >= 0 {
+ // Line of stars present.
+ if i > 0 && prefix[i-1] == ' ' {
+ i--; // remove trailing blank from prefix so stars remain aligned
+ }
+ prefix = prefix[0:i];
+ lineOfStars = true;
+ } else {
+ // No line of stars present.
+ // Determine the white space on the first line after the /*
+ // and before the beginning of the comment text, assume two
+ // blanks instead of the /* unless the first character after
+ // the /* is a tab. This whitespace may be found as suffix
+ // in the common prefix.
+ first := lines[0];
+ suffix := make([]byte, len(first));
+ n := 2;
+ for n < len(first) && first[n] <= ' ' {
+ suffix[n] = first[n];
+ n++;
+ }
+ if n > 2 && suffix[2] == '\t' {
+ // assume the '\t' compensates for the /*
+ suffix = suffix[2:n];
+ } else {
+ // otherwise assume two blanks
+ suffix[0], suffix[1] = ' ', ' ';
+ suffix = suffix[0:n];
+ }
+ // Shorten the computed common prefix by the length of
+ // suffix, if it is found as suffix of the prefix.
+ if bytes.HasSuffix(prefix, suffix) {
+ prefix = prefix[0 : len(prefix) - len(suffix)];
+ }
+ }
+
+ // Handle last line: If it only contains a closing */, align it
+ // with the opening /*, otherwise align the text with the other
+ // lines.
+ last := lines[len(lines)-1];
+ closing := []byte{'*', '/'};
+ i := bytes.Index(last, closing);
+ if isBlank(last[0:i]) {
+ // last line only contains closing */
+ var sep []byte;
+ if lineOfStars {
+ // insert an aligning blank
+ sep = []byte{' '};
+ }
+ lines[len(lines)-1] = bytes.Join([][]byte{prefix, closing}, sep);
+ } else {
+ // last line contains more comment text - assume
+ // it is aligned like the other lines
+ prefix = commonPrefix(prefix, last);
+ }
+
+ // Remove the common prefix from all but the first and empty lines.
+ for i, line := range lines {
+ if i > 0 && len(line) != 0 {
+ lines[i] = line[len(prefix) : len(line)];
}
}
- return dst[0 : j];
}
func (p *printer) writeComment(comment *ast.Comment) {
- // 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);
+
+ // shortcut common case of //-style comments
+ if text[1] == '/' {
+ p.writeCommentLine(comment, comment.Pos(), text);
+ return;
}
- // write comment
- var tag HtmlTag;
- if p.Styler != nil {
- text, tag = p.Styler.Comment(comment, text);
+ // for /*-style comments, print line by line and let the
+ // write function take care of the proper indentation
+ lines := split(text);
+ stripCommonPrefix(lines);
+
+ // write comment lines, separated by formfeed,
+ // without a line break after the last line
+ linebreak := []byte{byte(formfeed)};
+ pos := comment.Pos();
+ for i, line := range lines {
+ if i > 0 {
+ p.write(linebreak);
+ pos = p.pos;
+ }
+ if len(line) > 0 {
+ p.writeCommentLine(comment, pos, line);
+ }
}
- p.writeItem(comment.Pos(), text, tag);
}
// 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.
+// the comments and whitespace. The isKeyword parameter indicates if the next
+// token is a keyword or not.
//
-func (p *printer) intersperseComments(next token.Position) {
+func (p *printer) intersperseComments(next token.Position, isKeyword bool) {
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);
+ p.writeCommentPrefix(c.Pos(), next, isFirst, isKeyword);
isFirst = false;
p.writeComment(c);
needsLinebreak = c.Text[1] == '/';
next := p.pos; // estimated position of next item
var data []byte;
var tag HtmlTag;
+ isKeyword := false;
switch x := f.Interface().(type) {
case whiteSpace:
if x == ignore {
} else {
data = strings.Bytes(x.String());
}
+ isKeyword = x.IsKeyword();
case token.Position:
if x.IsValid() {
next = x; // accurate position of next item
p.pos = next;
if data != nil {
- p.flush(next);
+ p.flush(next, isKeyword);
// intersperse extra newlines if present in the source
// (don't do this in flush as it will cause extra newlines
// Flush prints any pending comments and whitespace occuring
// textually before the position of the next item.
//
-func (p *printer) flush(next token.Position) {
+func (p *printer) flush(next token.Position, isKeyword bool) {
// if there are comments before the next item, intersperse them
if p.commentBefore(next) {
- p.intersperseComments(next);
+ p.intersperseComments(next, isKeyword);
}
// write any leftover whitespace
p.writeWhitespace(len(p.buffer));
}
-func (p *printer) identList(list []*ast.Ident) {
+// Sets multiLine to true if the identifier list spans multiple lines.
+func (p *printer) identList(list []*ast.Ident, multiLine *bool) {
// convert into an expression list
xlist := make([]ast.Expr, len(list));
for i, x := range list {
xlist[i] = x;
}
- p.exprList(noPos, xlist, commaSep);
+ p.exprList(noPos, xlist, commaSep, multiLine);
}
-func (p *printer) stringList(list []*ast.BasicLit) {
+// Sets multiLine to true if the string list spans multiple lines.
+func (p *printer) stringList(list []*ast.BasicLit, multiLine *bool) {
// convert into an expression list
xlist := make([]ast.Expr, len(list));
for i, x := range list {
xlist[i] = x;
}
- p.exprList(noPos, xlist, noIndent);
+ p.exprList(noPos, xlist, noIndent, multiLine);
}
// Print a list of expressions. If the list spans multiple
// source lines, the original line breaks are respected between
-// expressions.
-func (p *printer) exprList(prev token.Position, list []ast.Expr, mode exprListMode) {
+// expressions. Sets multiLine to true if the list spans multiple
+// lines.
+func (p *printer) exprList(prev token.Position, list []ast.Expr, mode exprListMode, multiLine *bool) {
if len(list) == 0 {
return;
}
}
p.print(blank);
}
- p.expr(x);
+ p.expr(x, multiLine);
}
return;
}
if prev.IsValid() && prev.Line < line && p.linebreak(line, 1, 2, ws, true) {
ws = ignore;
+ *multiLine = true;
}
for i, x := range list {
if prev < line {
if p.linebreak(line, 1, 2, ws, true) {
ws = ignore;
+ *multiLine = true;
}
} else {
p.print(blank);
}
}
- p.expr(x);
+ p.expr(x, multiLine);
}
if mode & commaTerm != 0 {
p.print(token.COMMA);
}
-func (p *printer) parameters(list []*ast.Field) {
+// Sets multiLine to true if the the parameter list spans multiple lines.
+func (p *printer) parameters(list []*ast.Field, multiLine *bool) {
p.print(token.LPAREN);
if len(list) > 0 {
for i, par := range list {
p.print(token.COMMA, blank);
}
if len(par.Names) > 0 {
- p.identList(par.Names);
+ p.identList(par.Names, multiLine);
p.print(blank);
}
- p.expr(par.Type);
+ p.expr(par.Type, multiLine);
}
}
p.print(token.RPAREN);
// Returns true if a separating semicolon is optional.
-func (p *printer) signature(params, result []*ast.Field) (optSemi bool) {
- p.parameters(params);
+// Sets multiLine to true if the signature spans multiple lines.
+func (p *printer) signature(params, result []*ast.Field, multiLine *bool) (optSemi bool) {
+ p.parameters(params, multiLine);
if result != nil {
p.print(blank);
// single anonymous result; no ()'s unless it's a function type
f := result[0];
if _, isFtyp := f.Type.(*ast.FuncType); !isFtyp {
- optSemi = p.expr(f.Type);
+ optSemi = p.expr(f.Type, multiLine);
return;
}
}
- p.parameters(result);
+ p.parameters(result, multiLine);
}
return;
}
extraTabs := 0;
p.leadComment(f.Doc);
if len(f.Names) > 0 {
- p.identList(f.Names);
+ p.identList(f.Names, ignoreMultiLine);
p.print(sep);
- p.expr(f.Type);
+ p.expr(f.Type, ignoreMultiLine);
extraTabs = 1;
} else {
- p.expr(f.Type);
+ p.expr(f.Type, ignoreMultiLine);
extraTabs = 2;
}
if f.Tag != nil {
p.print(sep);
}
p.print(sep);
- p.expr(&ast.StringList{f.Tag});
+ p.expr(&ast.StringList{f.Tag}, ignoreMultiLine);
extraTabs = 0;
}
p.print(token.SEMICOLON);
p.leadComment(f.Doc);
if ftyp, isFtyp := f.Type.(*ast.FuncType); isFtyp {
// method
- p.expr(f.Names[0]); // exactly one name
- p.signature(ftyp.Params, ftyp.Results);
+ p.expr(f.Names[0], ignoreMultiLine); // exactly one name
+ p.signature(ftyp.Params, ftyp.Results, ignoreMultiLine);
} else {
// embedded interface
- p.expr(f.Type);
+ p.expr(f.Type, ignoreMultiLine);
}
p.print(token.SEMICOLON);
p.lineComment(f.Comment);
}
-func (p *printer) binaryExpr(x *ast.BinaryExpr, prec1 int) {
+// Sets multiLine to true if the binary expression spans multiple lines.
+func (p *printer) binaryExpr(x *ast.BinaryExpr, prec1 int, multiLine *bool) {
prec := x.Op.Precedence();
if prec < prec1 {
// parenthesis needed
// Note: The parser inserts an ast.ParenExpr node; thus this case
// can only occur if the AST is created in a different way.
p.print(token.LPAREN);
- p.expr(x);
+ p.expr(x, multiLine);
p.print(token.RPAREN);
return;
}
// Print collected operations left-to-right, with blanks if necessary.
ws := indent;
- p.expr1(x.X, prec);
+ p.expr1(x.X, prec, multiLine);
for list.Len() > 0 {
x = list.Pop().(*ast.BinaryExpr);
prev := line;
// in the source
if p.linebreak(line, 1, 2, ws, true) {
ws = ignore;
+ *multiLine = true;
}
} else {
p.print(blank, x.OpPos, x.Op, blank);
}
p.print(x.OpPos, x.Op);
}
- p.expr1(x.Y, prec);
+ p.expr1(x.Y, prec, multiLine);
}
if ws == ignore {
p.print(unindent);
// Returns true if a separating semicolon is optional.
-func (p *printer) expr1(expr ast.Expr, prec1 int) (optSemi bool) {
+// Sets multiLine to true if the expression spans multiple lines.
+func (p *printer) expr1(expr ast.Expr, prec1 int, multiLine *bool) (optSemi bool) {
p.print(expr.Pos());
switch x := expr.(type) {
p.print(x);
case *ast.BinaryExpr:
- p.binaryExpr(x, prec1);
+ p.binaryExpr(x, prec1, multiLine);
case *ast.KeyValueExpr:
- p.expr(x.Key);
+ p.expr(x.Key, multiLine);
p.print(x.Colon, token.COLON, blank);
- p.expr(x.Value);
+ p.expr(x.Value, multiLine);
case *ast.StarExpr:
p.print(token.MUL);
- optSemi = p.expr(x.X);
+ optSemi = p.expr(x.X, multiLine);
case *ast.UnaryExpr:
const prec = token.UnaryPrec;
if prec < prec1 {
// parenthesis needed
p.print(token.LPAREN);
- p.expr(x);
+ p.expr(x, multiLine);
p.print(token.RPAREN);
} else {
// no parenthesis needed
if x.Op == token.RANGE {
p.print(blank);
}
- p.expr1(x.X, prec);
+ p.expr1(x.X, prec, multiLine);
}
case *ast.BasicLit:
p.print(x);
case *ast.StringList:
- p.stringList(x.Strings);
+ p.stringList(x.Strings, multiLine);
case *ast.FuncLit:
- p.expr(x.Type);
- p.funcBody(x.Body, true);
+ p.expr(x.Type, multiLine);
+ p.funcBody(x.Body, true, multiLine);
case *ast.ParenExpr:
p.print(token.LPAREN);
- p.expr(x.X);
+ p.expr(x.X, multiLine);
p.print(x.Rparen, token.RPAREN);
case *ast.SelectorExpr:
- p.expr1(x.X, token.HighestPrec);
+ p.expr1(x.X, token.HighestPrec, multiLine);
p.print(token.PERIOD);
- p.expr1(x.Sel, token.HighestPrec);
+ p.expr1(x.Sel, token.HighestPrec, multiLine);
case *ast.TypeAssertExpr:
- p.expr1(x.X, token.HighestPrec);
+ p.expr1(x.X, token.HighestPrec, multiLine);
p.print(token.PERIOD, token.LPAREN);
if x.Type != nil {
- p.expr(x.Type);
+ p.expr(x.Type, multiLine);
} else {
p.print(token.TYPE);
}
p.print(token.RPAREN);
case *ast.IndexExpr:
- p.expr1(x.X, token.HighestPrec);
+ p.expr1(x.X, token.HighestPrec, multiLine);
p.print(token.LBRACK);
- p.expr1(x.Index, token.LowestPrec);
+ p.expr1(x.Index, token.LowestPrec, multiLine);
if x.End != nil {
if needsBlanks(x.Index) || needsBlanks(x.End) {
// blanks around ":"
// no blanks around ":"
p.print(token.COLON);
}
- p.expr(x.End);
+ p.expr(x.End, multiLine);
}
p.print(token.RBRACK);
case *ast.CallExpr:
- p.expr1(x.Fun, token.HighestPrec);
+ p.expr1(x.Fun, token.HighestPrec, multiLine);
p.print(x.Lparen, token.LPAREN);
- p.exprList(x.Lparen, x.Args, commaSep);
+ p.exprList(x.Lparen, x.Args, commaSep, multiLine);
p.print(x.Rparen, token.RPAREN);
case *ast.CompositeLit:
- p.expr1(x.Type, token.HighestPrec);
+ p.expr1(x.Type, token.HighestPrec, multiLine);
p.print(x.Lbrace, token.LBRACE);
- p.exprList(x.Lbrace, x.Elts, commaSep|commaTerm);
+ p.exprList(x.Lbrace, x.Elts, commaSep|commaTerm, multiLine);
p.print(x.Rbrace, token.RBRACE);
case *ast.Ellipsis:
case *ast.ArrayType:
p.print(token.LBRACK);
if x.Len != nil {
- p.expr(x.Len);
+ p.expr(x.Len, multiLine);
}
p.print(token.RBRACK);
- optSemi = p.expr(x.Elt);
+ optSemi = p.expr(x.Elt, multiLine);
case *ast.StructType:
p.print(token.STRUCT);
case *ast.FuncType:
p.print(token.FUNC);
- optSemi = p.signature(x.Params, x.Results);
+ optSemi = p.signature(x.Params, x.Results, multiLine);
case *ast.InterfaceType:
p.print(token.INTERFACE);
case *ast.MapType:
p.print(token.MAP, token.LBRACK);
- p.expr(x.Key);
+ p.expr(x.Key, multiLine);
p.print(token.RBRACK);
- optSemi = p.expr(x.Value);
+ optSemi = p.expr(x.Value, multiLine);
case *ast.ChanType:
switch x.Dir {
p.print(token.CHAN, token.ARROW);
}
p.print(blank);
- optSemi = p.expr(x.Value);
+ optSemi = p.expr(x.Value, multiLine);
default:
panic("unreachable");
// Returns true if a separating semicolon is optional.
-func (p *printer) expr(x ast.Expr) (optSemi bool) {
- return p.expr1(x, token.LowestPrec);
+// Sets multiLine to true if the expression spans multiple lines.
+func (p *printer) expr(x ast.Expr, multiLine *bool) (optSemi bool) {
+ return p.expr1(x, token.LowestPrec, multiLine);
}
if _indent > 0 {
p.print(indent);
}
+ var multiLine bool;
for i, s := range list {
// _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, ignore, i == 0 || _indent == 0);
- if !p.stmt(s) {
+ p.linebreak(s.Pos().Line, 1, maxStmtNewlines, ignore, i == 0 || _indent == 0 || multiLine);
+ multiLine = false;
+ if !p.stmt(s, &multiLine) {
p.print(token.SEMICOLON);
}
}
}
-func (p *printer) block(s *ast.BlockStmt, indent int) {
+// Sets multiLine to true if the block spans multiple lines.
+func (p *printer) block(s *ast.BlockStmt, indent int, multiLine *bool) {
p.print(s.Pos(), token.LBRACE);
if len(s.List) > 0 || p.commentBefore(s.Rbrace) {
p.stmtList(s.List, indent);
if init == nil && post == nil {
// no semicolons required
if expr != nil {
- p.expr(stripParens(expr));
+ p.expr(stripParens(expr), ignoreMultiLine);
needsBlank = true;
}
} else {
// all semicolons required
// (they are not separators, print them explicitly)
if init != nil {
- p.stmt(init);
+ p.stmt(init, ignoreMultiLine);
}
p.print(token.SEMICOLON, blank);
if expr != nil {
- p.expr(stripParens(expr));
+ p.expr(stripParens(expr), ignoreMultiLine);
needsBlank = true;
}
if isForStmt {
p.print(token.SEMICOLON, blank);
needsBlank = false;
if post != nil {
- p.stmt(post);
+ p.stmt(post, ignoreMultiLine);
needsBlank = true;
}
}
// Returns true if a separating semicolon is optional.
-func (p *printer) stmt(stmt ast.Stmt) (optSemi bool) {
+// Sets multiLine to true if the statements spans multiple lines.
+func (p *printer) stmt(stmt ast.Stmt, multiLine *bool) (optSemi bool) {
p.print(stmt.Pos());
switch s := stmt.(type) {
p.print("BadStmt");
case *ast.DeclStmt:
- p.decl(s.Decl, inStmtList);
+ p.decl(s.Decl, inStmtList, multiLine);
optSemi = true; // decl prints terminating semicolon if necessary
case *ast.EmptyStmt:
// is applied before the line break if there is no comment
// between (see writeWhitespace)
p.print(unindent);
- p.expr(s.Label);
+ p.expr(s.Label, multiLine);
p.print(token.COLON, vtab, indent);
p.linebreak(s.Stmt.Pos().Line, 0, 1, ignore, true);
- optSemi = p.stmt(s.Stmt);
+ optSemi = p.stmt(s.Stmt, multiLine);
case *ast.ExprStmt:
- p.expr(s.X);
+ p.expr(s.X, multiLine);
case *ast.IncDecStmt:
- p.expr(s.X);
+ p.expr(s.X, multiLine);
p.print(s.Tok);
case *ast.AssignStmt:
- p.exprList(s.Pos(), s.Lhs, commaSep);
+ p.exprList(s.Pos(), s.Lhs, commaSep, multiLine);
p.print(blank, s.TokPos, s.Tok);
- p.exprList(s.TokPos, s.Rhs, blankStart | commaSep);
+ p.exprList(s.TokPos, s.Rhs, blankStart | commaSep, multiLine);
case *ast.GoStmt:
p.print(token.GO, blank);
- p.expr(s.Call);
+ p.expr(s.Call, multiLine);
case *ast.DeferStmt:
p.print(token.DEFER, blank);
- p.expr(s.Call);
+ p.expr(s.Call, multiLine);
case *ast.ReturnStmt:
p.print(token.RETURN);
if s.Results != nil {
- p.exprList(s.Pos(), s.Results, blankStart | commaSep);
+ p.exprList(s.Pos(), s.Results, blankStart | commaSep, multiLine);
}
case *ast.BranchStmt:
p.print(s.Tok);
if s.Label != nil {
p.print(blank);
- p.expr(s.Label);
+ p.expr(s.Label, multiLine);
}
case *ast.BlockStmt:
- p.block(s, 1);
+ p.block(s, 1, multiLine);
optSemi = true;
case *ast.IfStmt:
p.print(token.IF);
p.controlClause(false, s.Init, s.Cond, nil);
- p.block(s.Body, 1);
+ p.block(s.Body, 1, multiLine);
optSemi = true;
if s.Else != nil {
p.print(blank, token.ELSE, blank);
switch s.Else.(type) {
case *ast.BlockStmt, *ast.IfStmt:
- optSemi = p.stmt(s.Else);
+ optSemi = p.stmt(s.Else, multiLine);
default:
p.print(token.LBRACE, indent, formfeed);
- p.stmt(s.Else);
+ p.stmt(s.Else, ignoreMultiLine);
p.print(unindent, formfeed, token.RBRACE);
+ *multiLine = true;
}
}
case *ast.CaseClause:
if s.Values != nil {
p.print(token.CASE);
- p.exprList(s.Pos(), s.Values, blankStart | commaSep);
+ p.exprList(s.Pos(), s.Values, blankStart | commaSep, multiLine);
} else {
p.print(token.DEFAULT);
}
case *ast.SwitchStmt:
p.print(token.SWITCH);
p.controlClause(false, s.Init, s.Tag, nil);
- p.block(s.Body, 0);
+ p.block(s.Body, 0, multiLine);
optSemi = true;
case *ast.TypeCaseClause:
if s.Types != nil {
p.print(token.CASE);
- p.exprList(s.Pos(), s.Types, blankStart | commaSep);
+ p.exprList(s.Pos(), s.Types, blankStart | commaSep, multiLine);
} else {
p.print(token.DEFAULT);
}
p.print(token.SWITCH);
if s.Init != nil {
p.print(blank);
- p.stmt(s.Init);
+ p.stmt(s.Init, multiLine);
p.print(token.SEMICOLON);
}
p.print(blank);
- p.stmt(s.Assign);
+ p.stmt(s.Assign, multiLine);
p.print(blank);
- p.block(s.Body, 0);
+ p.block(s.Body, 0, multiLine);
optSemi = true;
case *ast.CommClause:
if s.Rhs != nil {
p.print(token.CASE, blank);
if s.Lhs != nil {
- p.expr(s.Lhs);
+ p.expr(s.Lhs, multiLine);
p.print(blank, s.Tok, blank);
}
- p.expr(s.Rhs);
+ p.expr(s.Rhs, multiLine);
} else {
p.print(token.DEFAULT);
}
case *ast.SelectStmt:
p.print(token.SELECT, blank);
- p.block(s.Body, 0);
+ p.block(s.Body, 0, multiLine);
optSemi = true;
case *ast.ForStmt:
p.print(token.FOR);
p.controlClause(true, s.Init, s.Cond, s.Post);
- p.block(s.Body, 1);
+ p.block(s.Body, 1, multiLine);
optSemi = true;
case *ast.RangeStmt:
p.print(token.FOR, blank);
- p.expr(s.Key);
+ p.expr(s.Key, multiLine);
if s.Value != nil {
p.print(token.COMMA, blank);
- p.expr(s.Value);
+ p.expr(s.Value, multiLine);
}
p.print(blank, s.TokPos, s.Tok, blank, token.RANGE, blank);
- p.expr(s.X);
+ p.expr(s.X, multiLine);
p.print(blank);
- p.block(s.Body, 1);
+ p.block(s.Body, 1, multiLine);
optSemi = true;
default:
// The parameter n is the number of specs in the group; context specifies
// the surroundings of the declaration. Separating semicolons are printed
-// depending on the context.
+// depending on the context. Sets multiLine to true if the spec spans
+// multiple lines.
//
-func (p *printer) spec(spec ast.Spec, n int, context declContext) {
+func (p *printer) spec(spec ast.Spec, n int, context declContext, multiLine *bool) {
var (
optSemi bool; // true if a semicolon is optional
comment *ast.CommentGroup; // a line comment, if any
case *ast.ImportSpec:
p.leadComment(s.Doc);
if s.Name != nil {
- p.expr(s.Name);
+ p.expr(s.Name, multiLine);
p.print(blank);
}
- p.expr(&ast.StringList{s.Path});
+ p.expr(&ast.StringList{s.Path}, multiLine);
comment = s.Comment;
case *ast.ValueSpec:
p.leadComment(s.Doc);
- p.identList(s.Names); // always present
+ p.identList(s.Names, multiLine); // always present
if n == 1 {
if s.Type != nil {
p.print(blank);
- optSemi = p.expr(s.Type);
+ optSemi = p.expr(s.Type, multiLine);
}
if s.Values != nil {
p.print(blank, token.ASSIGN);
- p.exprList(noPos, s.Values, blankStart | commaSep);
+ p.exprList(noPos, s.Values, blankStart | commaSep, multiLine);
optSemi = false;
}
} else {
p.print(vtab);
}
if s.Type != nil {
- optSemi = p.expr(s.Type);
+ optSemi = p.expr(s.Type, multiLine);
extraTabs = 1;
}
if s.Values != nil {
p.print(vtab);
p.print(token.ASSIGN);
- p.exprList(noPos, s.Values, blankStart | commaSep);
+ p.exprList(noPos, s.Values, blankStart | commaSep, multiLine);
optSemi = false;
extraTabs = 0;
}
case *ast.TypeSpec:
p.leadComment(s.Doc);
- p.expr(s.Name);
+ p.expr(s.Name, multiLine);
if n == 1 {
p.print(blank);
} else {
p.print(vtab);
}
- optSemi = p.expr(s.Type);
+ optSemi = p.expr(s.Type, multiLine);
comment = s.Comment;
default:
}
-func (p *printer) genDecl(d *ast.GenDecl, context declContext) {
+// Sets multiLine to true if the declaration spans multiple lines.
+func (p *printer) genDecl(d *ast.GenDecl, context declContext, multiLine *bool) {
p.leadComment(d.Doc);
p.print(d.Pos(), d.Tok, blank);
p.print(d.Lparen, token.LPAREN);
if len(d.Specs) > 0 {
p.print(indent, formfeed);
+ var ml bool;
for i, s := range d.Specs {
if i > 0 {
- p.print(newline);
+ if ml {
+ p.print(formfeed);
+ } else {
+ p.print(newline);
+ }
}
- p.spec(s, len(d.Specs), inGroup);
+ ml = false;
+ p.spec(s, len(d.Specs), inGroup, &ml);
}
p.print(unindent, formfeed);
+ *multiLine = true;
}
p.print(d.Rparen, token.RPAREN);
} else {
// single declaration
- p.spec(d.Specs[0], 1, context);
+ p.spec(d.Specs[0], 1, context, multiLine);
}
}
}
-func (p *printer) funcBody(b *ast.BlockStmt, isLit bool) {
+// Sets multiLine to true if the function body spans multiple lines.
+func (p *printer) funcBody(b *ast.BlockStmt, isLit bool, multiLine *bool) {
if b == nil {
return;
}
sep = blank;
}
p.print(sep, b.Pos(), token.LBRACE, blank);
- p.stmt(b.List[0]);
+ p.stmt(b.List[0], ignoreMultiLine);
p.print(blank, b.Rbrace, token.RBRACE);
return;
}
p.print(blank);
- p.block(b, 1);
+ p.block(b, 1, multiLine);
}
-func (p *printer) funcDecl(d *ast.FuncDecl) {
+// Sets multiLine to true if the declaration spans multiple lines.
+func (p *printer) funcDecl(d *ast.FuncDecl, multiLine *bool) {
p.leadComment(d.Doc);
p.print(d.Pos(), token.FUNC, blank);
if recv := d.Recv; recv != nil {
// method: print receiver
p.print(token.LPAREN);
if len(recv.Names) > 0 {
- p.expr(recv.Names[0]);
+ p.expr(recv.Names[0], multiLine);
p.print(blank);
}
- p.expr(recv.Type);
+ p.expr(recv.Type, multiLine);
p.print(token.RPAREN, blank);
}
- p.expr(d.Name);
- p.signature(d.Type.Params, d.Type.Results);
- p.funcBody(d.Body, false);
+ p.expr(d.Name, multiLine);
+ p.signature(d.Type.Params, d.Type.Results, multiLine);
+ p.funcBody(d.Body, false, multiLine);
}
-func (p *printer) decl(decl ast.Decl, context declContext) {
+// Sets multiLine to true if the declaration spans multiple lines.
+func (p *printer) decl(decl ast.Decl, context declContext, multiLine *bool) {
switch d := decl.(type) {
case *ast.BadDecl:
p.print(d.Pos(), "BadDecl");
case *ast.GenDecl:
- p.genDecl(d, context);
+ p.genDecl(d, context, multiLine);
case *ast.FuncDecl:
- p.funcDecl(d);
+ p.funcDecl(d, multiLine);
default:
panic("unreachable");
}
func (p *printer) file(src *ast.File) {
p.leadComment(src.Doc);
p.print(src.Pos(), token.PACKAGE, blank);
- p.expr(src.Name);
+ p.expr(src.Name, ignoreMultiLine);
if len(src.Decls) > 0 {
tok := token.ILLEGAL;
min = 2;
}
p.linebreak(d.Pos().Line, min, maxDeclNewlines, ignore, false);
- p.decl(d, atTop);
+ p.decl(d, atTop, ignoreMultiLine);
}
}
go func() {
switch n := node.(type) {
case ast.Expr:
- p.expr(n);
+ p.expr(n, ignoreMultiLine);
case ast.Stmt:
- p.stmt(n);
+ p.stmt(n, ignoreMultiLine);
case ast.Decl:
- p.decl(n, atTop);
+ p.decl(n, atTop, ignoreMultiLine);
case *ast.File:
p.comment = n.Comments;
p.file(n);
p.errors <- os.NewError(fmt.Sprintf("printer.Fprint: unsupported node type %T", n));
runtime.Goexit();
}
- p.flush(token.Position{Offset: 1<<30, Line: 1<<30}); // flush to "infinity"
+ p.flush(token.Position{Offset: 1<<30, Line: 1<<30}, false); // flush to "infinity"
p.errors <- nil; // no errors
}();
err := <-p.errors; // wait for completion of goroutine