// Printing of common AST nodes.
-// Print as many newlines as necessary (at least one and and at most
+// 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.
+// first newline is printed as a formfeed. Returns true if any linebreak
+// was printed; returns false otherwise.
//
// TODO(gri): Reconsider signature (provide position instead of line)
//
-func (p *printer) linebreak(line, max int, newSection bool) {
- n := line - p.last.Line;
+func (p *printer) linebreak(line, min, max int, newSection bool) (printedBreak bool) {
+ n := line - p.pos.Line;
switch {
- case n < 1: n = 1;
+ case n < min: n = min;
case n > max: n = max;
}
- if newSection {
+ if n > 0 && newSection {
p.print(formfeed);
n--;
+ printedBreak = true;
}
for ; n > 0; n-- {
p.print(newline);
+ printedBreak = true;
}
+ return;
}
return;
}
+ if mode & blankStart != 0 {
+ p.print(blank);
+ }
+
if list[0].Pos().Line == list[len(list)-1].Pos().Line {
// all list entries on a single line
- if mode & blankStart != 0 {
- p.print(blank);
- }
for i, x := range list {
if i > 0 {
if mode & commaSep != 0 {
// list entries span multiple lines;
// use source code positions to guide line breaks
- p.print(+1, formfeed);
line := list[0].Pos().Line;
+ indented := false;
+ // 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) {
+ p.print(+1);
+ indented = true;
+ }
for i, x := range list {
prev := line;
line = x.Pos().Line;
p.print(token.COMMA);
}
if prev < line {
- p.print(newline);
+ // at least one linebreak, but respect an extra empty line
+ // in the source
+ if p.linebreak(x.Pos().Line, 1, 2, true) && !indented {
+ p.print(+1);
+ indented = true;
+ }
} else {
p.print(blank);
}
}
if mode & commaTerm != 0 {
p.print(token.COMMA);
+ if indented {
+ // 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 {
+ p.print(-1);
}
- p.print(-1, formfeed);
}
case *ast.ParenExpr:
// parenthesized expressions don't need blanks around them
return false;
+ case *ast.IndexExpr:
+ // index expressions don't need blanks if the indexed expressions are simple
+ 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
func (p *printer) stmtList(list []ast.Stmt, indent int) {
p.print(+indent);
for i, s := range list {
- p.linebreak(s.Pos().Line, maxStmtNewlines, i == 0);
+ // 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);
if !p.stmt(s) {
p.print(token.SEMICOLON);
}
p.print(s.Pos(), token.LBRACE);
if len(s.List) > 0 {
p.stmtList(s.List, indent);
- p.linebreak(s.Rbrace.Line, maxStmtNewlines, true);
+ p.linebreak(s.Rbrace.Line, 1, maxStmtNewlines, true);
}
p.print(s.Rbrace, token.RBRACE);
}
+// TODO(gri): Decide if this should be used more broadly. The printing code
+// knows when to insert parentheses for precedence reasons, but
+// need to be careful to keep them around type expressions.
+func stripParens(x ast.Expr) ast.Expr {
+ if px, hasParens := x.(*ast.ParenExpr); hasParens {
+ return stripParens(px.X);
+ }
+ return x;
+}
+
+
func (p *printer) controlClause(isForStmt bool, init ast.Stmt, expr ast.Expr, post ast.Stmt) {
p.print(blank);
needsBlank := false;
if init == nil && post == nil {
// no semicolons required
if expr != nil {
- p.expr(expr);
+ p.expr(stripParens(expr));
needsBlank = true;
}
} else {
}
p.print(token.SEMICOLON, blank);
if expr != nil {
- p.expr(expr);
+ p.expr(stripParens(expr));
needsBlank = true;
}
if isForStmt {
// nothing to do
case *ast.LabeledStmt:
- p.print(-1, formfeed);
+ // whitespace printing is delayed, thus indentation adjustments
+ // take place before the previous newline/formfeed is printed
+ p.print(-1);
p.expr(s.Label);
- p.print(token.COLON, tab, +1, formfeed);
+ p.print(token.COLON, tab, +1);
+ p.linebreak(s.Stmt.Pos().Line, 0, 1, true);
optSemi = p.stmt(s.Stmt);
case *ast.ExprStmt:
optSemi = true;
if s.Else != nil {
p.print(blank, token.ELSE, blank);
- optSemi = p.stmt(s.Else);
+ switch s.Else.(type) {
+ case *ast.BlockStmt, *ast.IfStmt:
+ optSemi = p.stmt(s.Else);
+ default:
+ p.print(token.LBRACE, +1, formfeed);
+ p.stmt(s.Else);
+ p.print(-1, formfeed, token.RBRACE);
+ }
}
case *ast.CaseClause:
const maxDeclNewlines = 3 // maximum number of newlines between declarations
+func declToken(decl ast.Decl) (tok token.Token) {
+ tok = token.ILLEGAL;
+ switch d := decl.(type) {
+ case *ast.GenDecl:
+ tok = d.Tok;
+ case *ast.FuncDecl:
+ tok = token.FUNC;
+ }
+ return;
+}
+
+
func (p *printer) file(src *ast.File) {
p.leadComment(src.Doc);
p.print(src.Pos(), token.PACKAGE, blank);
p.expr(src.Name);
if len(src.Decls) > 0 {
+ tok := token.ILLEGAL;
for _, d := range src.Decls {
- p.linebreak(d.Pos().Line, maxDeclNewlines, false);
+ prev := tok;
+ tok = declToken(d);
+ // if the declaration token changed (e.g., from CONST to TYPE)
+ // print an empty line between top-level declarations
+ min := 1;
+ if prev != tok {
+ min = 2;
+ }
+ p.linebreak(d.Pos().Line, min, maxDeclNewlines, false);
p.decl(d);
}
}
fffff(); // no blank between identifier and ()
gggggggggggg(x, y, z int) (); // hurray
}
+
+// formatting of variable declarations
+func _() {
+ type day struct { n int; short, long string };
+ var (
+ Sunday = day{ 0, "SUN", "Sunday" };
+ Monday = day{ 1, "MON", "Monday" };
+ Tuesday = day{ 2, "TUE", "Tuesday" };
+ Wednesday = day{ 3, "WED", "Wednesday" };
+ Thursday = day{ 4, "THU", "Thursday" };
+ Friday = day{ 5, "FRI", "Friday" };
+ Saturday = day{ 6, "SAT", "Saturday" };
+ )
+}
+
+
+// formatting of consecutive single-line functions
+func _() {}
+func _() {}
+func _() {}
+
+func _() {} // an empty line before this function
+func _() {}
+func _() {}
// at least one empty line between declarations of different kind
import _ "io"
+
var _ int
fffff(); // no blank between identifier and ()
gggggggggggg(x, y, z int); // hurray
}
+
+// formatting of variable declarations
+func _() {
+ type day struct {
+ n int;
+ short, long string;
+ }
+ var (
+ Sunday = day{0, "SUN", "Sunday"};
+ Monday = day{1, "MON", "Monday"};
+ Tuesday = day{2, "TUE", "Tuesday"};
+ Wednesday = day{3, "WED", "Wednesday"};
+ Thursday = day{4, "THU", "Thursday"};
+ Friday = day{5, "FRI", "Friday"};
+ Saturday = day{6, "SAT", "Saturday"};
+ )
+}
+
+
+// formatting of consecutive single-line functions
+func _() {}
+func _() {}
+func _() {}
+
+func _() {} // an empty line before this function
+func _() {}
+func _() {}
_ = s[1:2];
_ = s[a:b];
_ = s[0:len(s)];
+ _ = s[0]<<1;
+ _ = (s[0]<<1)&0xf;
+ _ = s[0] << 2 | s[1] >> 4;
// spaces around expressions of different precedence or expressions containing spaces
_ = a + -b;
_ = s[a+b : len(s)];
_ = s[len(s) : -a];
_ = s[a : len(s)+1];
+ _ = s[a : len(s)+1]+s;
// spaces around operators with equal or lower precedence than comparisons
_ = a == b;
_ = ([]T){};
_ = (map[int]T){};
}
+
+
+func _() {
+ // TODO respect source line breaks in multi-line expressions
+ _ = a < b ||
+ b < a;
+ // TODO(gri): add more test cases
+ // TODO(gri): these comments should be indented
+}
_ = s[1:2];
_ = s[a:b];
_ = s[0:len(s)];
+ _ = s[0]<<1;
+ _ = (s[0]<<1)&0xf;
+ _ = s[0]<<2 | s[1]>>4;
// spaces around expressions of different precedence or expressions containing spaces
_ = a + -b;
_ = s[a+b : len(s)];
_ = s[len(s) : -a];
_ = s[a : len(s)+1];
+ _ = s[a : len(s)+1]+s;
// spaces around operators with equal or lower precedence than comparisons
_ = a == b;
_ = ([]T){};
_ = (map[int]T){};
}
+
+
+func _() {
+ // TODO respect source line breaks in multi-line expressions
+ _ = a < b || b < a;
+// TODO(gri): add more test cases
+// TODO(gri): these comments should be indented
+}
"testing";
)
+type writerTestEntry struct {
+ header *Header;
+ contents string;
+}
+
+type writerTest struct {
+ file string; // filename of expected output
+ entries []*writerTestEntry;
+}
+
+var writerTests = []*writerTest{
+ &writerTest{
+ file: "testdata/writer.tar",
+ entries: []*writerTestEntry{
+ &writerTestEntry{
+ header: &Header{
+ Name: "small.txt",
+ Mode: 0640,
+ Uid: 73025,
+ Gid: 5000,
+ Size: 5,
+ Mtime: 1246508266,
+ Typeflag: '0',
+ Uname: "dsymonds",
+ Gname: "eng",
+ },
+ contents: "Kilts",
+ },
+ &writerTestEntry{
+ header: &Header{
+ Name: "small2.txt",
+ Mode: 0640,
+ Uid: 73025,
+ Gid: 5000,
+ Size: 11,
+ Mtime: 1245217492,
+ Typeflag: '0',
+ Uname: "dsymonds",
+ Gname: "eng",
+ },
+ contents: "Google.com\n",
+ },
+ }
+ },
+ // The truncated test file was produced using these commands:
+ // dd if=/dev/zero bs=1048576 count=16384 > /tmp/16gig.txt
+ // tar -b 1 -c -f- /tmp/16gig.txt | dd bs=512 count=8 > writer-big.tar
+ &writerTest{
+ file: "testdata/writer-big.tar",
+ entries: []*writerTestEntry{
+ &writerTestEntry{
+ header: &Header{
+ Name: "tmp/16gig.txt",
+ Mode: 0640,
+ Uid: 73025,
+ Gid: 5000,
+ Size: 16 << 30,
+ Mtime: 1254699560,
+ Typeflag: '0',
+ Uname: "dsymonds",
+ Gname: "eng",
+ },
+ // no contents
+ },
+ },
+ },
+}
+
type untarTest struct {
file string;
headers []*Header;
"51185210916864000000000000000000000000",
}
+func usage() {
+ fmt.Fprintf(os.Stderr,
+ // TODO(gri): the 2nd string of this string list should not be indented
+ "usage: godoc package [name ...]\n"
+ " godoc -http=:6060\n"
+ );
+ flag.PrintDefaults();
+ os.Exit(2);
+}
+
func TestReader(t *testing.T) {
testLoop:
for i, test := range untarTests {
"testing";
)
+type writerTestEntry struct {
+ header *Header;
+ contents string;
+}
+
+type writerTest struct {
+ file string; // filename of expected output
+ entries []*writerTestEntry;
+}
+
+var writerTests = []*writerTest{
+ &writerTest{
+ file: "testdata/writer.tar",
+ entries: []*writerTestEntry{
+ &writerTestEntry{
+ header: &Header{
+ Name: "small.txt",
+ Mode: 0640,
+ Uid: 73025,
+ Gid: 5000,
+ Size: 5,
+ Mtime: 1246508266,
+ Typeflag: '0',
+ Uname: "dsymonds",
+ Gname: "eng",
+ },
+ contents: "Kilts",
+ },
+ &writerTestEntry{
+ header: &Header{
+ Name: "small2.txt",
+ Mode: 0640,
+ Uid: 73025,
+ Gid: 5000,
+ Size: 11,
+ Mtime: 1245217492,
+ Typeflag: '0',
+ Uname: "dsymonds",
+ Gname: "eng",
+ },
+ contents: "Google.com\n",
+ },
+ },
+ },
+ // The truncated test file was produced using these commands:
+ // dd if=/dev/zero bs=1048576 count=16384 > /tmp/16gig.txt
+ // tar -b 1 -c -f- /tmp/16gig.txt | dd bs=512 count=8 > writer-big.tar
+ &writerTest{
+ file: "testdata/writer-big.tar",
+ entries: []*writerTestEntry{&writerTestEntry{header: &Header{
+ Name: "tmp/16gig.txt",
+ Mode: 0640,
+ Uid: 73025,
+ Gid: 5000,
+ Size: 16<<30,
+ Mtime: 1254699560,
+ Typeflag: '0',
+ Uname: "dsymonds",
+ Gname: "eng",
+ }
+ // no contents
+ }},
+ },
+}
+
type untarTest struct {
file string;
headers []*Header;
2: "2",
10: "3628800",
20: "2432902008176640000",
- 100:
- "933262154439441526816992388562667004907159682643816214685929"
+ 100: "933262154439441526816992388562667004907159682643816214685929"
"638952175999932299156089414639761565182862536979208272237582"
- "51185210916864000000000000000000000000"
- ,
+ "51185210916864000000000000000000000000",
}
-func TestReader(t *testing.T) {
+func usage() {
+ fmt.Fprintf(os.Stderr,
+ // TODO(gri): the 2nd string of this string list should not be indented
+ "usage: godoc package [name ...]\n"
+ " godoc -http=:6060\n");
+ flag.PrintDefaults();
+ os.Exit(2);
+}
+func TestReader(t *testing.T) {
testLoop:
for i, test := range untarTests {
f, err := os.Open(test.file, os.O_RDONLY, 0444);
continue testLoop;
}
if !reflect.DeepEqual(hdr, header) {
- t.Errorf(
- "test %d, entry %d: Incorrect header:\nhave %+v\nwant %+v",
- i, j, *hdr, *header
- );
+ t.Errorf("test %d, entry %d: Incorrect header:\nhave %+v\nwant %+v",
+ i, j, *hdr, *header);
}
}
hdr, err := tr.Next();
if;{} // no semicolon printed
if expr{}
if;expr{} // no semicolon printed
+ if (expr){} // no parens printed
+ if;((expr)){} // no semicolon and parens printed
if x:=expr;{
use(x)}
if x:=expr; expr {use(x)}
switch;{} // no semicolon printed
switch expr {}
switch;expr{} // no semicolon printed
+ switch (expr) {} // no parens printed
+ switch;((expr)){} // no semicolon and parens printed
switch x := expr; { default:use(
x)
}
use(x);
use(x);
}
+
+ switch x {
+ case 0:
+ use(x);
+ case 1: // this comment should have no effect on the previous or next line
+ use(x);
+ }
}
func _() {
for{}
for expr {}
- for;;{} // no semicolon printed
+ for (expr) {} // no parens printed
+ for;;{} // no semicolons printed
for x :=expr;; {use( x)}
- for; expr;{} // no semicolon printed
+ for; expr;{} // no semicolons printed
+ for; ((expr));{} // no semicolons and parens printed
for; ; expr = false {}
for x :=expr; expr; {use(x)}
for x := expr;; expr=false {use(x)}
}
}
+
+
+// Formatting around labels.
+func _() {
+ L:
+}
+
+
+func _() {
+ L: _ = 0;
+}
+
+
+func _() {
+ for {
+ L1: _ = 0;
+ L2:
+ _ = 0;
+ }
+}
if {} // no semicolon printed
if expr {}
if expr {} // no semicolon printed
+ if expr {} // no parens printed
+ if expr {} // no semicolon and parens printed
if x := expr; {
use(x);
}
switch {} // no semicolon printed
switch expr {}
switch expr {} // no semicolon printed
+ switch expr {} // no parens printed
+ switch expr {} // no semicolon and parens printed
switch x := expr; {
default:
use(x);
use(x);
use(x);
}
+
+ switch x {
+ case 0:
+ use(x);
+ case 1: // this comment should have no effect on the previous or next line
+ use(x);
+ }
}
func _() {
for {}
for expr {}
- for {} // no semicolon printed
+ for expr {} // no parens printed
+ for {} // no semicolons printed
for x := expr; ; {
use(x);
}
- for expr {} // no semicolon printed
+ for expr {} // no semicolons printed
+ for expr {} // no semicolons and parens printed
for ; ; expr = false {}
for x := expr; expr; {
use(x);
}
}
+
+
+// Formatting around labels.
+func _() {
+L:
+ ;
+}
+
+
+func _() {
+L: _ = 0;
+}
+
+
+func _() {
+ for {
+ L1: _ = 0;
+ L2:
+ _ = 0;
+ }
+}