summaryrefslogtreecommitdiff
path: root/src/pkg/go/printer/printer.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/pkg/go/printer/printer.go')
-rw-r--r--src/pkg/go/printer/printer.go196
1 files changed, 84 insertions, 112 deletions
diff --git a/src/pkg/go/printer/printer.go b/src/pkg/go/printer/printer.go
index 90d9784ac..697a83fa8 100644
--- a/src/pkg/go/printer/printer.go
+++ b/src/pkg/go/printer/printer.go
@@ -34,12 +34,6 @@ 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'}
@@ -81,8 +75,9 @@ type printer struct {
mode pmode // current printer mode
lastTok token.Token // the last token printed (token.ILLEGAL if it's whitespace)
- // Buffered whitespace
- buffer []whiteSpace
+ // Reused buffers
+ wsbuf []whiteSpace // delayed white space
+ litbuf bytes.Buffer // for creation of escaped literals and comments
// The (possibly estimated) position in the generated output;
// in AST space (i.e., pos is set whenever a token position is
@@ -94,22 +89,23 @@ type printer struct {
// written using writeItem.
last token.Position
- // HTML support
- lastTaggedLine int // last line for which a line tag was written
-
// The list of all source comments, in order of appearance.
comments []*ast.CommentGroup // may be nil
cindex int // current comment index
useNodeComments bool // if not set, ignore lead and line comments of nodes
+
+ // Cache of already computed node sizes.
+ nodeSizes map[ast.Node]int
}
-func (p *printer) init(output io.Writer, cfg *Config, fset *token.FileSet) {
+func (p *printer) init(output io.Writer, cfg *Config, fset *token.FileSet, nodeSizes map[ast.Node]int) {
p.output = output
p.Config = *cfg
p.fset = fset
p.errors = make(chan os.Error)
- p.buffer = make([]whiteSpace, 0, 16) // whitespace sequences are short
+ p.wsbuf = make([]whiteSpace, 0, 16) // whitespace sequences are short
+ p.nodeSizes = nodeSizes
}
@@ -122,6 +118,20 @@ func (p *printer) internalError(msg ...interface{}) {
}
+// escape escapes string s by bracketing it with tabwriter.Escape.
+// Escaped strings pass through tabwriter unchanged. (Note that
+// valid Go programs cannot contain tabwriter.Escape bytes since
+// they do not appear in legal UTF-8 sequences).
+//
+func (p *printer) escape(s string) string {
+ p.litbuf.Reset()
+ p.litbuf.WriteByte(tabwriter.Escape)
+ p.litbuf.WriteString(s)
+ p.litbuf.WriteByte(tabwriter.Escape)
+ return p.litbuf.String()
+}
+
+
// nlines returns the adjusted number of linebreaks given the desired number
// of breaks n such that min <= result <= max where max depends on the current
// nesting level.
@@ -229,7 +239,7 @@ func (p *printer) writeNewlines(n int, useFF bool) {
// source text. writeItem updates p.last to the position immediately following
// the data.
//
-func (p *printer) writeItem(pos token.Position, data []byte) {
+func (p *printer) writeItem(pos token.Position, data string) {
if pos.IsValid() {
// continue with previous position if we don't have a valid pos
if p.last.IsValid() && p.last.Filename != pos.Filename {
@@ -238,7 +248,7 @@ func (p *printer) writeItem(pos token.Position, data []byte) {
// e.g., the result of ast.MergePackageFiles)
p.indent = 0
p.mode = 0
- p.buffer = p.buffer[0:0]
+ p.wsbuf = p.wsbuf[0:0]
}
p.pos = pos
}
@@ -247,7 +257,7 @@ func (p *printer) writeItem(pos token.Position, data []byte) {
_, filename := filepath.Split(pos.Filename)
p.write0([]byte(fmt.Sprintf("[%s:%d:%d]", filename, pos.Line, pos.Column)))
}
- p.write(data)
+ p.write([]byte(data))
p.last = p.pos
}
@@ -279,11 +289,11 @@ func (p *printer) writeCommentPrefix(pos, next token.Position, prev *ast.Comment
if prev == nil {
// first comment of a comment group
j := 0
- for i, ch := range p.buffer {
+ for i, ch := range p.wsbuf {
switch ch {
case blank:
// ignore any blanks before a comment
- p.buffer[i] = ignore
+ p.wsbuf[i] = ignore
continue
case vtab:
// respect existing tabs - important
@@ -317,11 +327,11 @@ func (p *printer) writeCommentPrefix(pos, next token.Position, prev *ast.Comment
if prev == nil {
// first comment of a comment group
j := 0
- for i, ch := range p.buffer {
+ for i, ch := range p.wsbuf {
switch ch {
case blank, vtab:
// ignore any horizontal whitespace before line breaks
- p.buffer[i] = ignore
+ p.wsbuf[i] = ignore
continue
case indent:
// apply pending indentation
@@ -338,7 +348,7 @@ func (p *printer) writeCommentPrefix(pos, next token.Position, prev *ast.Comment
}
case newline, formfeed:
// TODO(gri): may want to keep formfeed info in some cases
- p.buffer[i] = ignore
+ p.wsbuf[i] = ignore
}
j = i
break
@@ -359,12 +369,8 @@ func (p *printer) writeCommentPrefix(pos, next token.Position, prev *ast.Comment
}
-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)
- p.writeItem(pos, line)
-}
-
+// TODO(gri): It should be possible to convert the code below from using
+// []byte to string and in the process eliminate some conversions.
// Split comment text into lines
func split(text []byte) [][]byte {
@@ -545,13 +551,13 @@ func (p *printer) writeComment(comment *ast.Comment) {
// shortcut common case of //-style comments
if text[1] == '/' {
- p.writeCommentLine(comment, p.fset.Position(comment.Pos()), text)
+ p.writeItem(p.fset.Position(comment.Pos()), p.escape(text))
return
}
// for /*-style comments, print line by line and let the
// write function take care of the proper indentation
- lines := split(text)
+ lines := split([]byte(text))
stripCommonPrefix(lines)
// write comment lines, separated by formfeed,
@@ -564,7 +570,7 @@ func (p *printer) writeComment(comment *ast.Comment) {
pos = p.pos
}
if len(line) > 0 {
- p.writeCommentLine(comment, pos, line)
+ p.writeItem(pos, p.escape(string(line)))
}
}
}
@@ -577,11 +583,11 @@ func (p *printer) writeComment(comment *ast.Comment) {
// formfeed was dropped from the whitespace buffer.
//
func (p *printer) writeCommentSuffix(needsLinebreak bool) (droppedFF bool) {
- for i, ch := range p.buffer {
+ for i, ch := range p.wsbuf {
switch ch {
case blank, vtab:
// ignore trailing whitespace
- p.buffer[i] = ignore
+ p.wsbuf[i] = ignore
case indent, unindent:
// don't loose indentation information
case newline, formfeed:
@@ -593,11 +599,11 @@ func (p *printer) writeCommentSuffix(needsLinebreak bool) (droppedFF bool) {
if ch == formfeed {
droppedFF = true
}
- p.buffer[i] = ignore
+ p.wsbuf[i] = ignore
}
}
}
- p.writeWhitespace(len(p.buffer))
+ p.writeWhitespace(len(p.wsbuf))
// make sure we have a line break
if needsLinebreak {
@@ -651,7 +657,7 @@ func (p *printer) writeWhitespace(n int) {
// write entries
var data [1]byte
for i := 0; i < n; i++ {
- switch ch := p.buffer[i]; ch {
+ switch ch := p.wsbuf[i]; ch {
case ignore:
// ignore!
case indent:
@@ -669,13 +675,13 @@ func (p *printer) writeWhitespace(n int) {
// 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 {
+ if i+1 < n && p.wsbuf[i+1] == unindent {
// Use a formfeed to terminate the current section.
// Otherwise, a long label name on the next line leading
// to a wide column may increase the indentation column
// of lines before the label; effectively leading to wrong
// indentation.
- p.buffer[i], p.buffer[i+1] = unindent, formfeed
+ p.wsbuf[i], p.wsbuf[i+1] = unindent, formfeed
i-- // do it again
continue
}
@@ -688,11 +694,11 @@ func (p *printer) writeWhitespace(n int) {
// shift remaining entries down
i := 0
- for ; n < len(p.buffer); n++ {
- p.buffer[i] = p.buffer[n]
+ for ; n < len(p.wsbuf); n++ {
+ p.wsbuf[i] = p.wsbuf[n]
i++
}
- p.buffer = p.buffer[0:i]
+ p.wsbuf = p.wsbuf[0:i]
}
@@ -733,7 +739,7 @@ func mayCombine(prev token.Token, next byte) (b bool) {
func (p *printer) print(args ...interface{}) {
for _, f := range args {
next := p.pos // estimated position of next item
- var data []byte
+ var data string
var tok token.Token
switch x := f.(type) {
@@ -747,42 +753,22 @@ func (p *printer) print(args ...interface{}) {
// LabeledStmt)
break
}
- i := len(p.buffer)
- if i == cap(p.buffer) {
+ i := len(p.wsbuf)
+ if i == cap(p.wsbuf) {
// Whitespace sequences are very short so this should
// never happen. Handle gracefully (but possibly with
// bad comment placement) if it does happen.
p.writeWhitespace(i)
i = 0
}
- p.buffer = p.buffer[0 : i+1]
- p.buffer[i] = x
+ p.wsbuf = p.wsbuf[0 : i+1]
+ p.wsbuf[i] = x
case *ast.Ident:
- data = []byte(x.Name)
+ data = x.Name
tok = token.IDENT
case *ast.BasicLit:
- // 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)
- data = make([]byte, 0, len(x.Value)+2)
- data = append(data, tabwriter.Escape)
- data = append(data, x.Value...)
- data = append(data, tabwriter.Escape)
+ data = p.escape(x.Value)
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]) {
@@ -792,13 +778,13 @@ func (p *printer) print(args ...interface{}) {
// (except for token.INT followed by a '.' this
// should never happen because it is taken care
// of via binary expression formatting)
- if len(p.buffer) != 0 {
+ if len(p.wsbuf) != 0 {
p.internalError("whitespace buffer not empty")
}
- p.buffer = p.buffer[0:1]
- p.buffer[0] = ' '
+ p.wsbuf = p.wsbuf[0:1]
+ p.wsbuf[0] = ' '
}
- data = []byte(s)
+ data = s
tok = x
case token.Pos:
if x.IsValid() {
@@ -812,7 +798,7 @@ func (p *printer) print(args ...interface{}) {
p.lastTok = tok
p.pos = next
- if data != nil {
+ if data != "" {
droppedFF := p.flush(next, tok)
// intersperse extra newlines if present in the source
@@ -847,7 +833,7 @@ func (p *printer) flush(next token.Position, tok token.Token) (droppedFF bool) {
droppedFF = p.intersperseComments(next, tok)
} else {
// otherwise, write any leftover whitespace
- p.writeWhitespace(len(p.buffer))
+ p.writeWhitespace(len(p.wsbuf))
}
return
}
@@ -863,10 +849,9 @@ func (p *printer) flush(next token.Position, tok token.Token) (droppedFF bool) {
// through unchanged.
//
type trimmer struct {
- output io.Writer
- state int
- space bytes.Buffer
- hasText bool
+ output io.Writer
+ state int
+ space bytes.Buffer
}
@@ -874,15 +859,11 @@ type trimmer struct {
// It can be in one of the following states:
const (
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.
@@ -891,9 +872,8 @@ var backquote = []byte{'`'}
func (p *trimmer) Write(data []byte) (n int, err os.Error) {
// invariants:
- // p.state == inSpace, atEscape:
+ // p.state == inSpace:
// 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
@@ -910,32 +890,20 @@ func (p *trimmer) Write(data []byte) (n int, err os.Error) {
case '\n', '\f':
p.space.Reset() // discard trailing space
_, err = p.output.Write(newlines[0:1]) // write newline
- p.hasText = false
case tabwriter.Escape:
- p.state = atEscape
+ _, err = p.output.Write(p.space.Bytes())
+ p.state = inEscape
+ m = n + 1 // +1: skip tabwriter.Escape
default:
_, err = p.output.Write(p.space.Bytes())
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 {
@@ -944,19 +912,18 @@ func (p *trimmer) Write(data []byte) (n int, err os.Error) {
p.state = inSpace
p.space.Reset()
p.space.WriteByte(b) // WriteByte returns no errors
- 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 = atEscape
- p.space.Reset()
- p.hasText = true
+ p.state = inEscape
+ m = n + 1 // +1: skip tabwriter.Escape
}
+ default:
+ panic("unreachable")
}
if err != nil {
return
@@ -969,7 +936,6 @@ func (p *trimmer) Write(data []byte) (n int, err os.Error) {
_, err = p.output.Write(data[m:n])
p.state = inSpace
p.space.Reset()
- p.hasText = true
}
return
@@ -994,13 +960,8 @@ type Config struct {
}
-// Fprint "pretty-prints" an AST node to output and returns the number
-// of bytes written and an error (if any) for a given configuration cfg.
-// Position information is interpreted relative to the file set fset.
-// The node type must be *ast.File, or assignment-compatible to ast.Expr,
-// ast.Decl, ast.Spec, or ast.Stmt.
-//
-func (cfg *Config) Fprint(output io.Writer, fset *token.FileSet, node interface{}) (int, os.Error) {
+// fprint implements Fprint and takes a nodesSizes map for setting up the printer state.
+func (cfg *Config) fprint(output io.Writer, fset *token.FileSet, node interface{}, nodeSizes map[ast.Node]int) (int, os.Error) {
// redirect output through a trimmer to eliminate trailing whitespace
// (Input to a tabwriter must be untrimmed since trailing tabs provide
// formatting information. The tabwriter could provide trimming
@@ -1029,7 +990,7 @@ func (cfg *Config) Fprint(output io.Writer, fset *token.FileSet, node interface{
// setup printer and print node
var p printer
- p.init(output, cfg, fset)
+ p.init(output, cfg, fset, nodeSizes)
go func() {
switch n := node.(type) {
case ast.Expr:
@@ -1076,6 +1037,17 @@ func (cfg *Config) Fprint(output io.Writer, fset *token.FileSet, node interface{
}
+// Fprint "pretty-prints" an AST node to output and returns the number
+// of bytes written and an error (if any) for a given configuration cfg.
+// Position information is interpreted relative to the file set fset.
+// The node type must be *ast.File, or assignment-compatible to ast.Expr,
+// ast.Decl, ast.Spec, or ast.Stmt.
+//
+func (cfg *Config) Fprint(output io.Writer, fset *token.FileSet, node interface{}) (int, os.Error) {
+ return cfg.fprint(output, fset, node, make(map[ast.Node]int))
+}
+
+
// Fprint "pretty-prints" an AST node to output.
// It calls Config.Fprint with default settings.
//