diff options
author | Ondřej Surý <ondrej@sury.org> | 2011-02-18 09:50:58 +0100 |
---|---|---|
committer | Ondřej Surý <ondrej@sury.org> | 2011-02-18 09:50:58 +0100 |
commit | c072558b90f1bbedc2022b0f30c8b1ac4712538e (patch) | |
tree | 67767591619e4bd8111fb05fac185cde94fb7378 /src/pkg/go | |
parent | 5859517b767c99749a45651c15d4bae5520ebae8 (diff) | |
download | golang-c072558b90f1bbedc2022b0f30c8b1ac4712538e.tar.gz |
Imported Upstream version 2011.02.15upstream/2011.02.15
Diffstat (limited to 'src/pkg/go')
-rw-r--r-- | src/pkg/go/printer/printer.go | 281 | ||||
-rw-r--r-- | src/pkg/go/printer/printer_test.go | 37 | ||||
-rw-r--r-- | src/pkg/go/printer/testdata/expressions.golden | 71 | ||||
-rw-r--r-- | src/pkg/go/printer/testdata/expressions.input | 79 | ||||
-rw-r--r-- | src/pkg/go/printer/testdata/expressions.raw | 72 |
5 files changed, 370 insertions, 170 deletions
diff --git a/src/pkg/go/printer/printer.go b/src/pkg/go/printer/printer.go index 34b0c4e2d..48e2af1b7 100644 --- a/src/pkg/go/printer/printer.go +++ b/src/pkg/go/printer/printer.go @@ -34,18 +34,18 @@ const ( ) +const ( + esc2 = '\xfe' // an escape byte that cannot occur in regular UTF-8 + _ = 1 / (esc2 - tabwriter.Escape) // cause compiler error if esc2 == tabwriter.Escape +) + + var ( esc = []byte{tabwriter.Escape} 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 the max determined by nlines formfeeds = []byte("\f\f\f\f\f\f\f\f") // more than the max determined by nlines - - esc_quot = []byte(""") // shorter than """ - esc_apos = []byte("'") // shorter than "'" - esc_amp = []byte("&") - esc_lt = []byte("<") - esc_gt = []byte(">") ) @@ -145,18 +145,20 @@ func (p *printer) nlines(n, min int) int { // write0 does not indent after newlines, and does not HTML-escape or update p.pos. // func (p *printer) write0(data []byte) { - n, err := p.output.Write(data) - p.written += n - if err != nil { - p.errors <- err - runtime.Goexit() + if len(data) > 0 { + n, err := p.output.Write(data) + p.written += n + if err != nil { + p.errors <- err + runtime.Goexit() + } } } // write interprets data and writes it to p.output. It inserts indentation -// after a line break unless in a tabwriter escape sequence, and it HTML- -// escapes characters if GenHTML is set. It updates p.pos as a side-effect. +// after a line break unless in a tabwriter escape sequence. +// It updates p.pos as a side-effect. // func (p *printer) write(data []byte) { i0 := 0 @@ -189,36 +191,6 @@ func (p *printer) write(data []byte) { // next segment start i0 = i + 1 - case '"', '\'', '&', '<', '>': - if p.Mode&GenHTML != 0 { - // write segment ending in b - p.write0(data[i0:i]) - - // write HTML-escaped b - var esc []byte - switch b { - case '"': - esc = esc_quot - case '\'': - esc = esc_apos - case '&': - esc = esc_amp - case '<': - esc = esc_lt - case '>': - esc = esc_gt - } - p.write0(esc) - - // update p.pos - d := i + 1 - i0 - p.pos.Offset += d - p.pos.Column += d - - // next segment start - i0 = i + 1 - } - case tabwriter.Escape: p.mode ^= inLiteral @@ -251,29 +223,13 @@ func (p *printer) writeNewlines(n int, useFF bool) { } -func (p *printer) writeTaggedItem(data []byte, tag HTMLTag) { - // write start tag, if any - // (no html-escaping and no p.pos update for tags - use write0) - if tag.Start != "" { - p.write0([]byte(tag.Start)) - } - p.write(data) - // write end tag, if any - if tag.End != "" { - p.write0([]byte(tag.End)) - } -} - - // 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. If tags are present and GenHTML is set, the tags are written -// before and after the data. writeItem updates p.last to the position -// immediately following the data. +// source text. writeItem updates p.last to the position immediately following +// the data. // -func (p *printer) writeItem(pos token.Position, data []byte, tag HTMLTag) { - fileChanged := false +func (p *printer) writeItem(pos token.Position, data []byte) { if pos.IsValid() { // continue with previous position if we don't have a valid pos if p.last.IsValid() && p.last.Filename != pos.Filename { @@ -283,7 +239,6 @@ func (p *printer) writeItem(pos token.Position, data []byte, tag HTMLTag) { p.indent = 0 p.mode = 0 p.buffer = p.buffer[0:0] - fileChanged = true } p.pos = pos } @@ -292,18 +247,7 @@ func (p *printer) writeItem(pos token.Position, data []byte, tag HTMLTag) { _, filename := path.Split(pos.Filename) p.write0([]byte(fmt.Sprintf("[%s:%d:%d]", filename, pos.Line, pos.Column))) } - if p.Mode&GenHTML != 0 { - // write line tag if on a new line - // TODO(gri): should write line tags on each line at the start - // will be more useful (e.g. to show line numbers) - if p.Styler != nil && (pos.Line != p.lastTaggedLine || fileChanged) { - p.writeTaggedItem(p.Styler.LineTag(pos.Line)) - p.lastTaggedLine = pos.Line - } - p.writeTaggedItem(data, tag) - } else { - p.write(data) - } + p.write(data) p.last = p.pos } @@ -312,14 +256,13 @@ func (p *printer) writeItem(pos token.Position, data []byte, tag HTMLTag) { // If there is any pending whitespace, it consumes as much of // it as is likely to help position the comment nicely. // 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. +// after all pending comments, prev is the previous comment in +// a group of comments (or nil), and isKeyword indicates if the +// next item is a keyword. // -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 +func (p *printer) writeCommentPrefix(pos, next token.Position, prev *ast.Comment, isKeyword bool) { + if p.written == 0 { + // the comment is the first item to be printed - don't write any whitespace return } @@ -329,11 +272,12 @@ func (p *printer) writeCommentPrefix(pos, next token.Position, isFirst, isKeywor return } - if pos.IsValid() && pos.Line == p.last.Line { + if pos.Line == p.last.Line && (prev == nil || prev.Text[1] != '/') { // comment on the same line as last item: // separate with at least one separator hasSep := false - if isFirst { + if prev == nil { + // first comment of a comment group j := 0 for i, ch := range p.buffer { switch ch { @@ -370,7 +314,8 @@ func (p *printer) writeCommentPrefix(pos, next token.Position, isFirst, isKeywor } else { // comment on a different line: // separate with at least one line break - if isFirst { + if prev == nil { + // first comment of a comment group j := 0 for i, ch := range p.buffer { switch ch { @@ -402,10 +347,14 @@ func (p *printer) writeCommentPrefix(pos, next token.Position, isFirst, isKeywor } // use formfeeds to break columns before a comment; // this is analogous to using formfeeds to separate - // individual lines of /*-style comments - // (if !pos.IsValid(), pos.Line == 0, and this will - // print no newlines) - p.writeNewlines(pos.Line-p.last.Line, true) + // individual lines of /*-style comments - but make + // sure there is at least one line break if the previous + // comment was a line comment + n := pos.Line - p.last.Line // if !pos.IsValid(), pos.Line == 0, and n will be 0 + if n <= 0 && prev != nil && prev.Text[1] == '/' { + n = 1 + } + p.writeNewlines(n, true) } } @@ -413,21 +362,10 @@ func (p *printer) writeCommentPrefix(pos, next token.Position, isFirst, isKeywor func (p *printer) writeCommentLine(comment *ast.Comment, pos token.Position, line []byte) { // line must pass through unchanged, bracket it with 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) + p.writeItem(pos, line) } -// 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) @@ -680,7 +618,7 @@ func (p *printer) intersperseComments(next token.Position, tok token.Token) (dro var last *ast.Comment for ; p.commentBefore(next); p.cindex++ { for _, c := range p.comments[p.cindex].List { - p.writeCommentPrefix(p.fset.Position(c.Pos()), next, last == nil, tok.IsKeyword()) + p.writeCommentPrefix(p.fset.Position(c.Pos()), next, last, tok.IsKeyword()) p.writeComment(c) last = c } @@ -796,7 +734,6 @@ func (p *printer) print(args ...interface{}) { for _, f := range args { next := p.pos // estimated position of next item var data []byte - var tag HTMLTag var tok token.Token switch x := f.(type) { @@ -821,28 +758,31 @@ func (p *printer) print(args ...interface{}) { p.buffer = p.buffer[0 : i+1] p.buffer[i] = x case *ast.Ident: - if p.Styler != nil { - data, tag = p.Styler.Ident(x) - } else { - data = []byte(x.Name) - } + data = []byte(x.Name) tok = token.IDENT case *ast.BasicLit: - if p.Styler != nil { - data, tag = p.Styler.BasicLit(x) - } else { - data = x.Value - } // escape all literals so they pass through unchanged // (note that valid Go programs cannot contain // tabwriter.Escape bytes since they do not appear in // legal UTF-8 sequences) - escData := make([]byte, 0, len(data)+2) - escData = append(escData, tabwriter.Escape) - escData = append(escData, data...) - escData = append(escData, tabwriter.Escape) - data = escData + data = make([]byte, 0, len(x.Value)+2) + data = append(data, tabwriter.Escape) + data = append(data, x.Value...) + data = append(data, tabwriter.Escape) tok = x.Kind + // If we have a raw string that spans multiple lines and + // the opening quote (`) is on a line preceded only by + // indentation, we don't want to write that indentation + // because the following lines of the raw string are not + // indented. It's easiest to correct the output at the end + // via the trimmer (because of the complex handling of + // white space). + // Mark multi-line raw strings by replacing the opening + // quote with esc2 and have the trimmer take care of fixing + // it up. (Do this _after_ making a copy of data!) + if data[1] == '`' && bytes.IndexByte(data, '\n') > 0 { + data[1] = esc2 + } case token.Token: s := x.String() if mayCombine(p.lastTok, s[0]) { @@ -858,11 +798,7 @@ func (p *printer) print(args ...interface{}) { p.buffer = p.buffer[0:1] p.buffer[0] = ' ' } - if p.Styler != nil { - data, tag = p.Styler.Token(x) - } else { - data = []byte(s) - } + data = []byte(s) tok = x case token.Pos: if x.IsValid() { @@ -885,7 +821,7 @@ func (p *printer) print(args ...interface{}) { // before p.writeNewlines(next.Line-p.pos.Line, droppedFF) - p.writeItem(next, data, tag) + p.writeItem(next, data) } } } @@ -927,21 +863,26 @@ func (p *printer) flush(next token.Position, tok token.Token) (droppedFF bool) { // through unchanged. // type trimmer struct { - output io.Writer - space bytes.Buffer - state int + output io.Writer + state int + space bytes.Buffer + hasText bool } // trimmer is implemented as a state machine. // It can be in one of the following states: const ( - inSpace = iota - inEscape - inText + inSpace = iota // inside space + atEscape // inside space and the last char was an opening tabwriter.Escape + inEscape // inside text bracketed by tabwriter.Escapes + inText // inside text ) +var backquote = []byte{'`'} + + // Design note: It is tempting to eliminate extra blanks occurring in // whitespace in this function as it could simplify some // of the blanks logic in the node printing functions. @@ -949,7 +890,13 @@ const ( // the tabwriter. func (p *trimmer) Write(data []byte) (n int, err os.Error) { - m := 0 // if p.state != inSpace, data[m:n] is unwritten + // invariants: + // p.state == inSpace, atEscape: + // p.space is unwritten + // p.hasText indicates if there is any text on this line + // p.state == inEscape, inText: + // data[m:n] is unwritten + m := 0 var b byte for n, b = range data { if b == '\v' { @@ -960,37 +907,55 @@ func (p *trimmer) Write(data []byte) (n int, err os.Error) { switch b { case '\t', ' ': p.space.WriteByte(b) // WriteByte returns no errors - case '\f', '\n': + case '\n', '\f': p.space.Reset() // discard trailing space _, err = p.output.Write(newlines[0:1]) // write newline + p.hasText = false case tabwriter.Escape: - _, err = p.output.Write(p.space.Bytes()) - p.space.Reset() - p.state = inEscape - m = n + 1 // drop tabwriter.Escape + p.state = atEscape default: _, err = p.output.Write(p.space.Bytes()) - p.space.Reset() p.state = inText m = n } + case atEscape: + // discard indentation if we have a multi-line raw string + // (see printer.print for details) + if b != esc2 || p.hasText { + _, err = p.output.Write(p.space.Bytes()) + } + p.state = inEscape + m = n + if b == esc2 { + _, err = p.output.Write(backquote) // convert back + m++ + } case inEscape: if b == tabwriter.Escape { _, err = p.output.Write(data[m:n]) p.state = inSpace + p.space.Reset() + p.hasText = true } case inText: switch b { case '\t', ' ': _, err = p.output.Write(data[m:n]) p.state = inSpace + p.space.Reset() p.space.WriteByte(b) // WriteByte returns no errors - case '\f': - data[n] = '\n' // convert to newline + p.hasText = true + case '\n', '\f': + _, err = p.output.Write(data[m:n]) + p.state = inSpace + p.space.Reset() + _, err = p.output.Write(newlines[0:1]) // write newline + p.hasText = false case tabwriter.Escape: _, err = p.output.Write(data[m:n]) - p.state = inEscape - m = n + 1 // drop tabwriter.Escape + p.state = atEscape + p.space.Reset() + p.hasText = true } } if err != nil { @@ -999,9 +964,12 @@ func (p *trimmer) Write(data []byte) (n int, err os.Error) { } n = len(data) - if p.state != inSpace { + switch p.state { + case inEscape, inText: _, err = p.output.Write(data[m:n]) p.state = inSpace + p.space.Reset() + p.hasText = true } return @@ -1013,36 +981,16 @@ func (p *trimmer) Write(data []byte) (n int, err os.Error) { // General printing is controlled with these Config.Mode flags. const ( - GenHTML uint = 1 << iota // generate HTML - RawFormat // do not use a tabwriter; if set, UseSpaces is ignored + RawFormat uint = 1 << iota // do not use a tabwriter; if set, UseSpaces is ignored TabIndent // use tabs for indentation independent of UseSpaces UseSpaces // use spaces instead of tabs for alignment ) -// An HTMLTag specifies a start and end tag. -type HTMLTag struct { - Start, End string // empty if tags are absent -} - - -// A Styler specifies formatting of line tags and elementary Go words. -// A format consists of text and a (possibly empty) surrounding HTML tag. -// -type Styler interface { - LineTag(line int) ([]byte, HTMLTag) - Comment(c *ast.Comment, line []byte) ([]byte, HTMLTag) - BasicLit(x *ast.BasicLit) ([]byte, HTMLTag) - Ident(id *ast.Ident) ([]byte, HTMLTag) - Token(tok token.Token) ([]byte, HTMLTag) -} - - // A Config node controls the output of Fprint. type Config struct { - Mode uint // default: 0 - Tabwidth int // default: 8 - Styler Styler // default: nil + Mode uint // default: 0 + Tabwidth int // default: 8 } @@ -1070,9 +1018,6 @@ func (cfg *Config) Fprint(output io.Writer, fset *token.FileSet, node interface{ } twmode := tabwriter.DiscardEmptyColumns - if cfg.Mode&GenHTML != 0 { - twmode |= tabwriter.FilterHTML - } if cfg.Mode&TabIndent != 0 { minwidth = 0 twmode |= tabwriter.TabIndent diff --git a/src/pkg/go/printer/printer_test.go b/src/pkg/go/printer/printer_test.go index c66471b92..565075aa2 100644 --- a/src/pkg/go/printer/printer_test.go +++ b/src/pkg/go/printer/printer_test.go @@ -127,7 +127,7 @@ var data = []entry{ } -func Test(t *testing.T) { +func TestFiles(t *testing.T) { for _, e := range data { source := path.Join(dataDir, e.source) golden := path.Join(dataDir, e.golden) @@ -136,3 +136,38 @@ func Test(t *testing.T) { //check(t, golden, golden, e.mode); } } + + +// TestLineComments, using a simple test case, checks that consequtive line +// comments are properly terminated with a newline even if the AST position +// information is incorrect. +// +func TestLineComments(t *testing.T) { + const src = `// comment 1 + // comment 2 + // comment 3 + package main + ` + + fset := token.NewFileSet() + ast1, err1 := parser.ParseFile(fset, "", src, parser.ParseComments) + if err1 != nil { + panic(err1) + } + + var buf bytes.Buffer + fset = token.NewFileSet() // use the wrong file set + Fprint(&buf, fset, ast1) + + nlines := 0 + for _, ch := range buf.Bytes() { + if ch == '\n' { + nlines++ + } + } + + const expected = 3 + if nlines < expected { + t.Errorf("got %d, expected %d\n", nlines, expected) + } +} diff --git a/src/pkg/go/printer/testdata/expressions.golden b/src/pkg/go/printer/testdata/expressions.golden index 882c7624c..7f18f338a 100644 --- a/src/pkg/go/printer/testdata/expressions.golden +++ b/src/pkg/go/printer/testdata/expressions.golden @@ -248,6 +248,77 @@ they must not be removed` func _() { + // smart handling of indentation for multi-line raw strings + var _ = `` + var _ = `foo` + var _ = `foo +bar` + + var _ = `` + var _ = `foo` + var _ = + // the next line should not be indented +`foo +bar` + + var _ = // comment + `` + var _ = // comment + `foo` + var _ = // comment + // the next line should not be indented +`foo +bar` + + var _ = /* comment */ `` + var _ = /* comment */ `foo` + var _ = /* comment */ `foo +bar` + + var _ = /* comment */ + `` + var _ = /* comment */ + `foo` + var _ = /* comment */ + // the next line should not be indented +`foo +bar` + + var board = []int( +`........... +........... +....●●●.... +....●●●.... +..●●●●●●●.. +..●●●○●●●.. +..●●●●●●●.. +....●●●.... +....●●●.... +........... +........... +`) + + var state = S{ + "foo", + // the next line should not be indented +`........... +........... +....●●●.... +....●●●.... +..●●●●●●●.. +..●●●○●●●.. +..●●●●●●●.. +....●●●.... +....●●●.... +........... +........... +`, + "bar", + } +} + + +func _() { // one-line function literals (body is on a single line) _ = func() {} _ = func() int { return 0 } diff --git a/src/pkg/go/printer/testdata/expressions.input b/src/pkg/go/printer/testdata/expressions.input index 647706b09..6bcd9b5f8 100644 --- a/src/pkg/go/printer/testdata/expressions.input +++ b/src/pkg/go/printer/testdata/expressions.input @@ -244,6 +244,85 @@ they must not be removed` func _() { + // smart handling of indentation for multi-line raw strings + var _ = `` + var _ = `foo` + var _ = `foo +bar` + + +var _ = + `` +var _ = + `foo` +var _ = + // the next line should not be indented + `foo +bar` + + + var _ = // comment + `` + var _ = // comment + `foo` + var _ = // comment + // the next line should not be indented + `foo +bar` + + +var _ = /* comment */ `` +var _ = /* comment */ `foo` +var _ = /* comment */ `foo +bar` + + + var _ = /* comment */ + `` + var _ = /* comment */ + `foo` + var _ = /* comment */ + // the next line should not be indented + `foo +bar` + + +var board = []int( + `........... +........... +....●●●.... +....●●●.... +..●●●●●●●.. +..●●●○●●●.. +..●●●●●●●.. +....●●●.... +....●●●.... +........... +........... +`) + + + var state = S{ + "foo", + // the next line should not be indented + `........... +........... +....●●●.... +....●●●.... +..●●●●●●●.. +..●●●○●●●.. +..●●●●●●●.. +....●●●.... +....●●●.... +........... +........... +`, + "bar", + } +} + + +func _() { // one-line function literals (body is on a single line) _ = func() {} _ = func() int { return 0 } diff --git a/src/pkg/go/printer/testdata/expressions.raw b/src/pkg/go/printer/testdata/expressions.raw index 62be00cc3..f1944c94b 100644 --- a/src/pkg/go/printer/testdata/expressions.raw +++ b/src/pkg/go/printer/testdata/expressions.raw @@ -243,7 +243,77 @@ func _() { _ = `foo bar` _ = `three spaces before the end of the line starting here: -they must not be removed` +they must not be removed`} + + +func _() { + // smart handling of indentation for multi-line raw strings + var _ = `` + var _ = `foo` + var _ = `foo +bar` + + var _ = `` + var _ = `foo` + var _ = + // the next line should not be indented +`foo +bar` + + var _ = // comment + `` + var _ = // comment + `foo` + var _ = // comment + // the next line should not be indented +`foo +bar` + + var _ = /* comment */ `` + var _ = /* comment */ `foo` + var _ = /* comment */ `foo +bar` + + var _ = /* comment */ + `` + var _ = /* comment */ + `foo` + var _ = /* comment */ + // the next line should not be indented +`foo +bar` + + var board = []int( +`........... +........... +....●●●.... +....●●●.... +..●●●●●●●.. +..●●●○●●●.. +..●●●●●●●.. +....●●●.... +....●●●.... +........... +........... +`) + + var state = S{ + "foo", + // the next line should not be indented +`........... +........... +....●●●.... +....●●●.... +..●●●●●●●.. +..●●●○●●●.. +..●●●●●●●.. +....●●●.... +....●●●.... +........... +........... +`, + "bar", + } } |