1
0
mirror of https://github.com/golang/go synced 2024-11-12 09:50:21 -07:00

- rewrite declaration printing to take full use of discardable tabwriter columns

- honor line breaks in multi-line expressions
- do not add extra indentation to multi-line string lists
- don't put blanks around simple function calls and conversions
- do not modify `` strings
- added extra test cases

R=rsc
DELTA=398  (246 added, 51 deleted, 101 changed)
OCL=35453
CL=35465
This commit is contained in:
Robert Griesemer 2009-10-08 08:48:33 -07:00
parent 093af4e512
commit 7ecfb021f3
6 changed files with 335 additions and 140 deletions

View File

@ -40,14 +40,14 @@ type whiteSpace int
const (
blank = whiteSpace(' ');
tab = whiteSpace('\t');
vtab = whiteSpace('\v');
newline = whiteSpace('\n');
formfeed = whiteSpace('\f');
)
var (
tabs = [...]byte{'\t', '\t', '\t', '\t', '\t', '\t', '\t', '\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("&");
lessthan = strings.Bytes("<");
@ -111,23 +111,32 @@ func (p *printer) write0(data []byte) {
}
type writeMode uint;
const (
writeRaw writeMode = 1<<iota; // do not interpret newline/formfeed characters
setLineTag; // wrap item with a line tag
)
// write interprets data and writes it to p.output. It inserts indentation
// after newline or formfeed and HTML-escapes characters if GenHTML is set.
//
func (p *printer) write(data []byte) {
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 indentation
// use horizontal ("hard") tabs - indentation columns
// must not be discarded by the tabwriter
j := p.indent;
for ; j > len(tabs); j -= len(tabs) {
p.write0(&tabs);
for ; j > len(htabs); j -= len(htabs) {
p.write0(&htabs);
}
p.write0(tabs[0 : j]);
p.write0(htabs[0 : j]);
// update p.pos
p.pos.Offset += i+1 - i0 + p.indent;
@ -136,6 +145,7 @@ func (p *printer) write(data []byte) {
// next segment start
i0 = i+1;
}
case '&', '<', '>':
if p.mode & GenHTML != 0 {
@ -176,12 +186,12 @@ func (p *printer) writeNewlines(n int) {
if n > maxNewlines {
n = maxNewlines;
}
p.write(newlines[0 : n]);
p.write(newlines[0 : n], 0);
}
}
func (p *printer) writeItem(pos token.Position, data []byte, setLineTag bool) {
func (p *printer) writeItem(pos token.Position, data []byte, mode writeMode) {
p.pos = pos;
if debug {
// do not update p.pos - use write0
@ -189,7 +199,7 @@ func (p *printer) writeItem(pos token.Position, data []byte, setLineTag bool) {
}
if p.mode & GenHTML != 0 {
// no html-escaping and no p.pos update for tags - use write0
if setLineTag && pos.Line > p.lastTaggedLine {
if mode & setLineTag != 0 && pos.Line > p.lastTaggedLine {
// id's must be unique within a document: set
// line tag only if line number has increased
// (note: for now write complete start and end
@ -203,14 +213,14 @@ func (p *printer) writeItem(pos token.Position, data []byte, setLineTag bool) {
p.write0(strings.Bytes(p.tag.start));
p.tag.start = ""; // tag consumed
}
p.write(data);
p.write(data, mode);
// write end tag, if any
if p.tag.end != "" {
p.write0(strings.Bytes(p.tag.end));
p.tag.end = ""; // tag consumed
}
} else {
p.write(data);
p.write(data, mode);
}
p.last = p.pos;
}
@ -242,7 +252,7 @@ func (p *printer) writeComment(comment *ast.Comment) {
n := comment.Pos().Line - p.last.Line;
if n == 0 {
// comment on the same line as last item; separate with tab
p.write(tabs[0 : 1]);
p.write(htabs[0 : 1], 0);
} else {
// comment on a different line; separate with newlines
p.writeNewlines(n);
@ -250,7 +260,7 @@ func (p *printer) writeComment(comment *ast.Comment) {
}
// write comment
p.writeItem(comment.Pos(), comment.Text, false);
p.writeItem(comment.Pos(), comment.Text, 0);
}
@ -318,7 +328,7 @@ func (p *printer) writeWhitespace() {
b = b[0 : p.buflen];
p.buflen = 0;
p.write(b);
p.write(b, 0);
}
@ -334,7 +344,7 @@ func (p *printer) writeWhitespace() {
// printed, followed by the actual token.
//
func (p *printer) print(args ...) {
setLineTag := false;
var mode writeMode;
v := reflect.NewValue(args).(*reflect.StructValue);
for i := 0; i < v.NumField(); i++ {
f := v.Field(i);
@ -359,6 +369,10 @@ func (p *printer) print(args ...) {
p.buflen++;
case []byte:
data = x;
// do not modify multi-line `` strings!
if len(x) > 0 && x[0] == '`' && x[len(x)-1] == '`' {
mode |= writeRaw;
}
case string:
data = strings.Bytes(x);
case token.Token:
@ -371,7 +385,7 @@ func (p *printer) print(args ...) {
pos := token.Position(x);
if pos.IsValid() {
next = pos; // accurate position of next item
setLineTag = true;
mode |= setLineTag;
}
case htmlTag:
p.tag = x; // tag surrounding next item
@ -386,8 +400,8 @@ func (p *printer) print(args ...) {
// intersperse extra newlines if present in the source
p.writeNewlines(next.Line - p.pos.Line);
p.writeItem(next, data, setLineTag);
setLineTag = false;
p.writeItem(next, data, mode);
mode = 0;
}
}
}
@ -463,13 +477,15 @@ func (p *printer) leadComment(d *ast.CommentGroup) {
}
// Print a tab followed by a line comment.
// Print n tabs followed by a line comment.
// A newline must be printed afterwards since
// the comment may be a //-style comment.
func (p *printer) lineComment(d *ast.CommentGroup) {
func (p *printer) lineComment(n int, d *ast.CommentGroup) {
// Ignore the comment if we have comments interspersed (p.comment != nil).
if p.comment == nil && d != nil {
p.print(tab);
for ; n > 0; n-- {
p.print(vtab);
}
p.commentList(d.List);
}
}
@ -491,7 +507,7 @@ func (p *printer) stringList(list []*ast.BasicLit) {
for i, x := range list {
xlist[i] = x;
}
p.exprList(xlist, 0);
p.exprList(xlist, noIndent);
}
@ -500,6 +516,7 @@ const (
blankStart exprListMode = 1 << iota; // print a blank before the list
commaSep; // elements are separated by commas
commaTerm; // elements are terminated by comma
noIndent; // no extra indentation in multi-line lists
)
@ -531,10 +548,12 @@ func (p *printer) exprList(list []ast.Expr, mode exprListMode) {
// list entries span multiple lines;
// use source code positions to guide line breaks
line := list[0].Pos().Line;
indented := false;
// 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
if p.linebreak(line, 0, 2, true) {
if p.linebreak(line, 0, 2, true) && !indented {
p.print(+1);
indented = true;
}
@ -560,13 +579,13 @@ func (p *printer) exprList(list []ast.Expr, mode exprListMode) {
}
if mode & commaTerm != 0 {
p.print(token.COMMA);
if indented {
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(formfeed); // terminating comma needs a line break to look good
} else if indented {
} else if indented && mode&noIndent == 0 {
p.print(-1);
}
}
@ -612,14 +631,6 @@ func (p *printer) signature(params, result []*ast.Field) (optSemi bool) {
}
func separator(useTab bool) whiteSpace {
if useTab {
return tab;
}
return blank;
}
func (p *printer) fieldList(lbrace token.Position, list []*ast.Field, rbrace token.Position, isIncomplete, isStruct bool) {
if len(list) == 0 && !isIncomplete {
// no blank between keyword and {} in this case
@ -631,7 +642,10 @@ func (p *printer) fieldList(lbrace token.Position, list []*ast.Field, rbrace tok
// at least one entry or incomplete
p.print(blank, lbrace, token.LBRACE, +1, formfeed);
if isStruct {
sep := separator(len(list) > 1);
sep := blank;
if len(list) > 1 {
sep = vtab;
}
for i, f := range list {
p.leadComment(f.Doc);
if len(f.Names) > 0 {
@ -644,7 +658,7 @@ func (p *printer) fieldList(lbrace token.Position, list []*ast.Field, rbrace tok
p.expr(&ast.StringList{f.Tag});
}
p.print(token.SEMICOLON);
p.lineComment(f.Comment);
p.lineComment(1, f.Comment);
if i+1 < len(list) || isIncomplete {
p.print(newline);
}
@ -664,7 +678,7 @@ func (p *printer) fieldList(lbrace token.Position, list []*ast.Field, rbrace tok
p.expr(f.Type);
}
p.print(token.SEMICOLON);
p.lineComment(f.Comment);
p.lineComment(1, f.Comment);
if i+1 < len(list) || isIncomplete {
p.print(newline);
}
@ -696,14 +710,13 @@ func needsBlanks(expr ast.Expr) bool {
return needsBlanks(x.X)
case *ast.CallExpr:
// call expressions need blanks if they have more than one
// argument or if the function or the argument need blanks
return len(x.Args) > 1 || needsBlanks(x.Fun) || len(x.Args) == 1 && needsBlanks(x.Args[0]);
// argument or if the function expression needs blanks
return len(x.Args) > 1 || needsBlanks(x.Fun);
}
return true;
}
// TODO(gri): Write this recursively; get rid of vector use.
func (p *printer) binaryExpr(x *ast.BinaryExpr, prec1 int) {
prec := x.Op.Precedence();
if prec < prec1 {
@ -717,39 +730,63 @@ func (p *printer) binaryExpr(x *ast.BinaryExpr, prec1 int) {
}
// Traverse left, collect all operations at same precedence
// and determine if blanks should be printed.
// and determine if blanks should be printed around operators.
//
// This algorithm assumes that the right-hand side of a binary
// operation has a different (higher) precedence then the current
// node, which is how the parser creates the AST.
var list vector.Vector;
line := x.Y.Pos().Line;
printBlanks := prec <= token.EQL.Precedence() || needsBlanks(x.Y);
for {
list.Push(x);
if t, ok := x.X.(*ast.BinaryExpr); ok && t.Op.Precedence() == prec {
x = t;
if needsBlanks(x.Y) {
prev := line;
line = x.Y.Pos().Line;
if needsBlanks(x.Y) || prev != line {
printBlanks = true;
}
} else {
break;
}
}
if needsBlanks(x.X) {
prev := line;
line = x.X.Pos().Line;
if needsBlanks(x.X) || prev != line {
printBlanks = true;
}
// Print collected operations left-to-right, with blanks if necessary.
indented := false;
p.expr1(x.X, prec);
for list.Len() > 0 {
x = list.Pop().(*ast.BinaryExpr);
prev := line;
line = x.Y.Pos().Line;
if printBlanks {
p.print(blank, x.OpPos, x.Op, blank);
if prev != line {
p.print(blank, x.OpPos, x.Op);
// at least one linebreak, but respect an extra empty line
// in the source
if p.linebreak(line, 1, 2, false) && !indented {
p.print(+1);
indented = true;
}
} else {
p.print(blank, x.OpPos, x.Op, blank);
}
} else {
if prev != line {
panic("internal error");
}
p.print(x.OpPos, x.Op);
}
p.expr1(x.Y, prec);
}
if indented {
p.print(-1);
}
}
@ -995,7 +1032,8 @@ func (p *printer) stmt(stmt ast.Stmt) (optSemi bool) {
p.print("BadStmt");
case *ast.DeclStmt:
optSemi = p.decl(s.Decl);
p.decl(s.Decl, inStmtList);
optSemi = true; // decl prints terminating semicolon if necessary
case *ast.EmptyStmt:
// nothing to do
@ -1005,7 +1043,7 @@ func (p *printer) stmt(stmt ast.Stmt) (optSemi bool) {
// take place before the previous newline/formfeed is printed
p.print(-1);
p.expr(s.Label);
p.print(token.COLON, tab, +1);
p.print(token.COLON, vtab, +1);
p.linebreak(s.Stmt.Pos().Line, 0, 1, true);
optSemi = p.stmt(s.Stmt);
@ -1154,55 +1192,81 @@ func (p *printer) stmt(stmt ast.Stmt) (optSemi bool) {
// ----------------------------------------------------------------------------
// Declarations
// Returns line comment, if any, and whether a separating semicolon is optional.
// The parameters m and n control layout; m has different meanings for different
// specs, n is the number of specs in the group.
type declContext uint;
const (
atTop declContext = iota;
inGroup;
inStmtList;
)
// 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.
//
// ImportSpec:
// m = number of imports with a rename
//
// ValueSpec:
// m = number of values with a type
//
func (p *printer) spec(spec ast.Spec, m, n int) (comment *ast.CommentGroup, optSemi bool) {
sep := separator(n > 1);
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
)
switch s := spec.(type) {
case *ast.ImportSpec:
p.leadComment(s.Doc);
if m > 0 {
// at least one entry with a rename
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(sep);
p.print(vtab); // column discarded if empty
}
p.expr(&ast.StringList{s.Path});
comment = s.Comment;
case *ast.ValueSpec:
p.leadComment(s.Doc);
p.identList(s.Names);
if m > 0 {
// at least one entry with a type
p.identList(s.Names); // never empty
if n == 1 {
if s.Type != nil {
p.print(sep);
p.print(blank);
optSemi = p.expr(s.Type);
} else if s.Values != nil {
p.print(sep);
}
}
if s.Values != nil {
p.print(sep, token.ASSIGN);
p.print(blank, token.ASSIGN);
p.exprList(s.Values, blankStart | commaSep);
optSemi = false;
}
} else {
columns = 2;
if s.Type != nil || s.Values != nil {
p.print(vtab);
}
if s.Type != nil {
optSemi = p.expr(s.Type);
columns = 1;
}
if s.Values != nil {
p.print(vtab);
p.print(token.ASSIGN);
p.exprList(s.Values, blankStart | commaSep);
optSemi = false;
columns = 0;
}
}
comment = s.Comment;
case *ast.TypeSpec:
p.leadComment(s.Doc);
p.expr(s.Name);
p.print(sep);
if n == 1 {
p.print(blank);
} else {
p.print(vtab);
}
optSemi = p.expr(s.Type);
comment = s.Comment;
@ -1210,32 +1274,15 @@ func (p *printer) spec(spec ast.Spec, m, n int) (comment *ast.CommentGroup, optS
panic("unreachable");
}
return comment, optSemi;
if context == inGroup || context == inStmtList && !optSemi {
p.print(token.SEMICOLON);
}
p.lineComment(1+columns, comment);
}
func countImportRenames(list []ast.Spec) (n int) {
for _, s := range list {
if s.(*ast.ImportSpec).Name != nil {
n++;
}
}
return;
}
func countValueTypes(list []ast.Spec) (n int) {
for _, s := range list {
if s.(*ast.ValueSpec).Type != nil {
n++;
}
}
return;
}
// Returns true if a separating semicolon is optional.
func (p *printer) decl(decl ast.Decl) (optSemi bool) {
func (p *printer) decl(decl ast.Decl, context declContext) {
switch d := decl.(type) {
case *ast.BadDecl:
p.print(d.Pos(), "BadDecl");
@ -1244,15 +1291,6 @@ func (p *printer) decl(decl ast.Decl) (optSemi bool) {
p.leadComment(d.Doc);
p.print(lineTag(d.Pos()), d.Tok, blank);
// determine layout constant m
var m int;
switch d.Tok {
case token.IMPORT:
m = countImportRenames(d.Specs);
case token.CONST, token.VAR:
m = countValueTypes(d.Specs);
}
if d.Lparen.IsValid() {
// group of parenthesized declarations
p.print(d.Lparen, token.LPAREN);
@ -1262,25 +1300,15 @@ func (p *printer) decl(decl ast.Decl) (optSemi bool) {
if i > 0 {
p.print(newline);
}
comment, _ := p.spec(s, m, len(d.Specs));
p.print(token.SEMICOLON);
p.lineComment(comment);
p.spec(s, len(d.Specs), inGroup);
}
p.print(-1, formfeed);
}
p.print(d.Rparen, token.RPAREN);
optSemi = true;
} else {
// single declaration
var comment *ast.CommentGroup;
comment, optSemi = p.spec(d.Specs[0], m, 1);
// If this declaration is inside a statement list, the parser
// does not associate a line comment with the declaration but
// handles it as ordinary unassociated comment. Thus, in that
// case, comment == nil and any trailing semicolon is not part
// of a comment.
p.lineComment(comment);
p.spec(d.Specs[0], 1, context);
}
case *ast.FuncDecl:
@ -1306,8 +1334,6 @@ func (p *printer) decl(decl ast.Decl) (optSemi bool) {
default:
panic("unreachable");
}
return;
}
@ -1345,7 +1371,7 @@ func (p *printer) file(src *ast.File) {
min = 2;
}
p.linebreak(d.Pos().Line, min, maxDeclNewlines, false);
p.decl(d);
p.decl(d, atTop);
}
}
@ -1451,9 +1477,9 @@ func Fprint(output io.Writer, node interface{}, mode uint, tabwidth int) (int, o
if mode & UseSpaces != 0 {
padchar = ' ';
}
var twmode uint;
twmode := tabwriter.DiscardEmptyColumns;
if mode & GenHTML != 0 {
twmode = tabwriter.FilterHTML;
twmode |= tabwriter.FilterHTML;
}
tw = tabwriter.NewWriter(output, tabwidth, 1, padchar, twmode);
output = tw;
@ -1469,7 +1495,7 @@ func Fprint(output io.Writer, node interface{}, mode uint, tabwidth int) (int, o
case ast.Stmt:
p.stmt(n);
case ast.Decl:
p.decl(n);
p.decl(n, atTop);
case *ast.File:
p.comment = n.Comments;
p.file(n);

View File

@ -147,6 +147,16 @@ func _() {
z = 2;
zzz = 3;
)
// no entry has a value
var (
_ int;
_ float;
_ string;
_ int; // comment
_ float; // comment
_ string; // comment
)
// some entries have a type
var (
xxxxxx int;
@ -157,6 +167,14 @@ func _() {
yyyy = "bar";
yyy string = "foo";
)
// mixed entries - all comments should be aligned
var (
a, b, c int;
x = 10;
d int; // comment
y = 20; // comment
f, ff, fff, ffff int = 0, 1, 2, 3; // comment
)
}
func _() {
@ -228,6 +246,14 @@ type _ struct {
}
// difficult cases
type _ struct {
bool; // comment
text []byte; // comment
}
// formatting of interfaces
type EI interface{}

View File

@ -146,6 +146,15 @@ func _() {
z = 2;
zzz = 3;
)
// no entry has a value
var (
_ int;
_ float;
_ string;
_ int; // comment
_ float; // comment
_ string; // comment
)
// some entries have a type
var (
xxxxxx int;
@ -156,6 +165,14 @@ func _() {
yyyy = "bar";
yyy string = "foo";
)
// mixed entries - all comments should be aligned
var (
a, b, c int;
x = 10;
d int; // comment
y = 20; // comment
f, ff, fff, ffff int = 0, 1, 2, 3; // comment
)
}
func _() {
@ -227,6 +244,13 @@ type _ struct {
}
// difficult cases
type _ struct {
bool; // comment
text []byte; // comment
}
// formatting of interfaces
type EI interface{}

View File

@ -39,6 +39,8 @@ func _() {
_ = "foo"+s;
_ = s+"foo";
_ = 'a'+'b';
_ = len(s)/2;
_ = len(t0.x)/a;
// spaces around expressions of different precedence or expressions containing spaces
_ = a + -b;
@ -80,6 +82,8 @@ func _() {
_ = (a+b+c)*2;
_ = a - b + c - d + (a+b+c) + d&e;
_ = under_bar-1;
_ = Open(dpath + "/file", O_WRONLY | O_CREAT, 0666);
_ = int(c0&_Mask4)<<18 | int(c1&_Maskx)<<12 | int(c2&_Maskx)<<6 | int(c3&_Maskx);
}
@ -101,9 +105,63 @@ func _() {
func _() {
// TODO respect source line breaks in multi-line expressions
// do not modify `` strings
_ = ``;
_ = `
`; // TODO(gri): fix line breaks here
_ = `foo
bar`;
}
func _() {
// not not add extra indentation to multi-line string lists
_ = "foo" "bar";
_ = "foo"
"bar"
"bah";
_ = []string {
"abc"
"def",
"foo"
"bar"
}
}
func _() {
// respect source lines in multi-line expressions
_ = a+
b+
c;
_ = a < b ||
b < a;
_ = "1234567890"
"1234567890";
// TODO(gri): add more test cases
// TODO(gri): these comments should be indented
}
func same(t, u *Time) bool {
// respect source lines in multi-line expressions
return t.Year == u.Year
&& t.Month == u.Month
&& t.Day == u.Day
&& t.Hour == u.Hour
&& t.Minute == u.Minute
&& t.Second == u.Second
&& t.Weekday == u.Weekday
&& t.ZoneOffset == u.ZoneOffset
&& t.Zone == u.Zone
}
func (p *parser) charClass() {
// respect source lines in multi-line expressions
if cc.negate && len(cc.ranges) == 2 &&
cc.ranges[0] == '\n' && cc.ranges[1] == '\n' {
nl := new(_NotNl);
p.re.add(nl);
}
}

View File

@ -39,6 +39,8 @@ func _() {
_ = "foo"+s;
_ = s+"foo";
_ = 'a'+'b';
_ = len(s)/2;
_ = len(t0.x)/a;
// spaces around expressions of different precedence or expressions containing spaces
_ = a + -b;
@ -80,6 +82,8 @@ func _() {
_ = (a+b+c)*2;
_ = a - b + c - d + (a+b+c) + d&e;
_ = under_bar - 1;
_ = Open(dpath+"/file", O_WRONLY|O_CREAT, 0666);
_ = int(c0&_Mask4)<<18 | int(c1&_Maskx)<<12 | int(c2&_Maskx)<<6 | int(c3&_Maskx);
}
@ -101,8 +105,65 @@ func _() {
func _() {
// TODO respect source line breaks in multi-line expressions
_ = a < b || b < a;
// do not modify `` strings
_ = ``;
_ = `
`;
// TODO(gri): fix line breaks here
_ = `foo
bar`;
}
func _() {
// not not add extra indentation to multi-line string lists
_ = "foo" "bar";
_ = "foo"
"bar"
"bah";
_ = []string{
"abc"
"def",
"foo"
"bar",
};
}
func _() {
// respect source lines in multi-line expressions
_ = a +
b +
c;
_ = a < b ||
b < a;
_ = "1234567890"
"1234567890";
// TODO(gri): add more test cases
// TODO(gri): these comments should be indented
}
func same(t, u *Time) bool {
// respect source lines in multi-line expressions
return t.Year == u.Year &&
t.Month == u.Month &&
t.Day == u.Day &&
t.Hour == u.Hour &&
t.Minute == u.Minute &&
t.Second == u.Second &&
t.Weekday == u.Weekday &&
t.ZoneOffset == u.ZoneOffset &&
t.Zone == u.Zone;
}
func (p *parser) charClass() {
// respect source lines in multi-line expressions
if cc.negate && len(cc.ranges) == 2 &&
cc.ranges[0] == '\n' && cc.ranges[1] == '\n' {
nl := new(_NotNl);
p.re.add(nl);
}
}