github.com/cilki/sh@v2.6.4+incompatible/syntax/printer.go (about)

     1  // Copyright (c) 2016, Daniel Martí <mvdan@mvdan.cc>
     2  // See LICENSE for licensing information
     3  
     4  package syntax
     5  
     6  import (
     7  	"bufio"
     8  	"bytes"
     9  	"fmt"
    10  	"io"
    11  	"strings"
    12  	"unicode"
    13  )
    14  
    15  // Indent sets the number of spaces used for indentation. If set to 0,
    16  // tabs will be used instead.
    17  func Indent(spaces uint) func(*Printer) {
    18  	return func(p *Printer) { p.indentSpaces = spaces }
    19  }
    20  
    21  // BinaryNextLine will make binary operators appear on the next line
    22  // when a binary command, such as a pipe, spans multiple lines. A
    23  // backslash will be used.
    24  func BinaryNextLine(p *Printer) { p.binNextLine = true }
    25  
    26  // SwitchCaseIndent will make switch cases be indented. As such, switch
    27  // case bodies will be two levels deeper than the switch itself.
    28  func SwitchCaseIndent(p *Printer) { p.swtCaseIndent = true }
    29  
    30  // SpaceRedirects will put a space after most redirection operators. The
    31  // exceptions are '>&', '<&', '>(', and '<('.
    32  func SpaceRedirects(p *Printer) { p.spaceRedirects = true }
    33  
    34  // KeepPadding will keep most nodes and tokens in the same column that
    35  // they were in the original source. This allows the user to decide how
    36  // to align and pad their code with spaces.
    37  //
    38  // Note that this feature is best-effort and will only keep the
    39  // alignment stable, so it may need some human help the first time it is
    40  // run.
    41  func KeepPadding(p *Printer) {
    42  	p.keepPadding = true
    43  	p.cols.Writer = p.bufWriter.(*bufio.Writer)
    44  	p.bufWriter = &p.cols
    45  }
    46  
    47  // Minify will print programs in a way to save the most bytes possible.
    48  // For example, indentation and comments are skipped, and extra
    49  // whitespace is avoided when possible.
    50  func Minify(p *Printer) { p.minify = true }
    51  
    52  // NewPrinter allocates a new Printer and applies any number of options.
    53  func NewPrinter(options ...func(*Printer)) *Printer {
    54  	p := &Printer{
    55  		bufWriter:   bufio.NewWriter(nil),
    56  		lenPrinter:  new(Printer),
    57  		tabsPrinter: new(Printer),
    58  	}
    59  	for _, opt := range options {
    60  		opt(p)
    61  	}
    62  	return p
    63  }
    64  
    65  // Print "pretty-prints" the given syntax tree node to the given writer. Writes
    66  // to w are buffered.
    67  //
    68  // The node types supported at the moment are *File, *Stmt, *Word, any Command
    69  // node, and any WordPart node. A trailing newline will only be printed when a
    70  // *File is used.
    71  func (p *Printer) Print(w io.Writer, node Node) error {
    72  	p.reset()
    73  	p.bufWriter.Reset(w)
    74  	switch x := node.(type) {
    75  	case *File:
    76  		p.stmtList(x.StmtList)
    77  		p.newline(x.End())
    78  	case *Stmt:
    79  		p.stmtList(StmtList{Stmts: []*Stmt{x}})
    80  	case Command:
    81  		p.line = x.Pos().Line()
    82  		p.command(x, nil)
    83  	case *Word:
    84  		p.word(x)
    85  	case WordPart:
    86  		p.wordPart(x, nil)
    87  	default:
    88  		return fmt.Errorf("unsupported node type: %T", x)
    89  	}
    90  	p.flushHeredocs()
    91  	p.flushComments()
    92  	return p.bufWriter.Flush()
    93  }
    94  
    95  type bufWriter interface {
    96  	Write([]byte) (int, error)
    97  	WriteString(string) (int, error)
    98  	WriteByte(byte) error
    99  	Reset(io.Writer)
   100  	Flush() error
   101  }
   102  
   103  type colCounter struct {
   104  	*bufio.Writer
   105  	column    int
   106  	lineStart bool
   107  }
   108  
   109  func (c *colCounter) WriteByte(b byte) error {
   110  	switch b {
   111  	case '\n':
   112  		c.column = 0
   113  		c.lineStart = true
   114  	case '\t', ' ':
   115  	default:
   116  		c.lineStart = false
   117  	}
   118  	c.column++
   119  	return c.Writer.WriteByte(b)
   120  }
   121  
   122  func (c *colCounter) WriteString(s string) (int, error) {
   123  	c.lineStart = false
   124  	for _, r := range s {
   125  		if r == '\n' {
   126  			c.column = 0
   127  		}
   128  		c.column++
   129  	}
   130  	return c.Writer.WriteString(s)
   131  }
   132  
   133  func (c *colCounter) Reset(w io.Writer) {
   134  	c.column = 1
   135  	c.lineStart = true
   136  	c.Writer.Reset(w)
   137  }
   138  
   139  // Printer holds the internal state of the printing mechanism of a
   140  // program.
   141  type Printer struct {
   142  	bufWriter
   143  	cols colCounter
   144  
   145  	indentSpaces   uint
   146  	binNextLine    bool
   147  	swtCaseIndent  bool
   148  	spaceRedirects bool
   149  	keepPadding    bool
   150  	minify         bool
   151  
   152  	wantSpace   bool
   153  	wantNewline bool
   154  	wroteSemi   bool
   155  
   156  	commentPadding uint
   157  
   158  	// pendingComments are any comments in the current line or statement
   159  	// that we have yet to print. This is useful because that way, we can
   160  	// ensure that all comments are written immediately before a newline.
   161  	// Otherwise, in some edge cases we might wrongly place words after a
   162  	// comment in the same line, breaking programs.
   163  	pendingComments []Comment
   164  
   165  	// firstLine means we are still writing the first line
   166  	firstLine bool
   167  	// line is the current line number
   168  	line uint
   169  
   170  	// lastLevel is the last level of indentation that was used.
   171  	lastLevel uint
   172  	// level is the current level of indentation.
   173  	level uint
   174  	// levelIncs records which indentation level increments actually
   175  	// took place, to revert them once their section ends.
   176  	levelIncs []bool
   177  
   178  	nestedBinary bool
   179  
   180  	// pendingHdocs is the list of pending heredocs to write.
   181  	pendingHdocs []*Redirect
   182  
   183  	// used in stmtCols to align comments
   184  	lenPrinter *Printer
   185  	lenCounter byteCounter
   186  
   187  	// used when printing <<- heredocs with tab indentation
   188  	tabsPrinter *Printer
   189  }
   190  
   191  func (p *Printer) reset() {
   192  	p.wantSpace, p.wantNewline = false, false
   193  	p.commentPadding = 0
   194  	p.pendingComments = p.pendingComments[:0]
   195  
   196  	// minification uses its own newline logic
   197  	p.firstLine = !p.minify
   198  	p.line = 0
   199  
   200  	p.lastLevel, p.level = 0, 0
   201  	p.levelIncs = p.levelIncs[:0]
   202  	p.nestedBinary = false
   203  	p.pendingHdocs = p.pendingHdocs[:0]
   204  }
   205  
   206  func (p *Printer) spaces(n uint) {
   207  	for i := uint(0); i < n; i++ {
   208  		p.WriteByte(' ')
   209  	}
   210  }
   211  
   212  func (p *Printer) space() {
   213  	p.WriteByte(' ')
   214  	p.wantSpace = false
   215  }
   216  
   217  func (p *Printer) spacePad(pos Pos) {
   218  	if p.wantSpace {
   219  		p.WriteByte(' ')
   220  		p.wantSpace = false
   221  	}
   222  	if p.cols.lineStart {
   223  		// Never add padding at the start of a line, since this may
   224  		// result in broken indentation or mixing of spaces and tabs.
   225  		return
   226  	}
   227  	for !p.cols.lineStart && p.cols.column > 0 && p.cols.column < int(pos.col) {
   228  		p.WriteByte(' ')
   229  	}
   230  }
   231  
   232  func (p *Printer) bslashNewl() {
   233  	if p.wantSpace {
   234  		p.space()
   235  	}
   236  	p.WriteString("\\\n")
   237  	p.line++
   238  	p.indent()
   239  }
   240  
   241  func (p *Printer) spacedString(s string, pos Pos) {
   242  	p.spacePad(pos)
   243  	p.WriteString(s)
   244  	p.wantSpace = true
   245  }
   246  
   247  func (p *Printer) spacedToken(s string, pos Pos) {
   248  	if p.minify {
   249  		p.WriteString(s)
   250  		p.wantSpace = false
   251  		return
   252  	}
   253  	p.spacePad(pos)
   254  	p.WriteString(s)
   255  	p.wantSpace = true
   256  }
   257  
   258  func (p *Printer) semiOrNewl(s string, pos Pos) {
   259  	if p.wantNewline {
   260  		p.newline(pos)
   261  		p.indent()
   262  	} else {
   263  		if !p.wroteSemi {
   264  			p.WriteByte(';')
   265  		}
   266  		if !p.minify {
   267  			p.space()
   268  		}
   269  		p.line = pos.Line()
   270  	}
   271  	p.WriteString(s)
   272  	p.wantSpace = true
   273  }
   274  
   275  func (p *Printer) incLevel() {
   276  	inc := false
   277  	if p.level <= p.lastLevel || len(p.levelIncs) == 0 {
   278  		p.level++
   279  		inc = true
   280  	} else if last := &p.levelIncs[len(p.levelIncs)-1]; *last {
   281  		*last = false
   282  		inc = true
   283  	}
   284  	p.levelIncs = append(p.levelIncs, inc)
   285  }
   286  
   287  func (p *Printer) decLevel() {
   288  	if p.levelIncs[len(p.levelIncs)-1] {
   289  		p.level--
   290  	}
   291  	p.levelIncs = p.levelIncs[:len(p.levelIncs)-1]
   292  }
   293  
   294  func (p *Printer) indent() {
   295  	if p.minify {
   296  		return
   297  	}
   298  	p.lastLevel = p.level
   299  	switch {
   300  	case p.level == 0:
   301  	case p.indentSpaces == 0:
   302  		for i := uint(0); i < p.level; i++ {
   303  			p.WriteByte('\t')
   304  		}
   305  	default:
   306  		p.spaces(p.indentSpaces * p.level)
   307  	}
   308  }
   309  
   310  func (p *Printer) newline(pos Pos) {
   311  	p.flushHeredocs()
   312  	p.flushComments()
   313  	p.WriteByte('\n')
   314  	p.wantNewline, p.wantSpace = false, false
   315  	if p.line < pos.Line() {
   316  		p.line++
   317  	}
   318  }
   319  
   320  func (p *Printer) flushHeredocs() {
   321  	if len(p.pendingHdocs) == 0 {
   322  		return
   323  	}
   324  	hdocs := p.pendingHdocs
   325  	p.pendingHdocs = p.pendingHdocs[:0]
   326  	coms := p.pendingComments
   327  	p.pendingComments = nil
   328  	if len(coms) > 0 {
   329  		c := coms[0]
   330  		if c.Pos().Line() == p.line {
   331  			p.pendingComments = append(p.pendingComments, c)
   332  			p.flushComments()
   333  			coms = coms[1:]
   334  		}
   335  	}
   336  
   337  	// Reuse the last indentation level, as
   338  	// indentation levels are usually changed before
   339  	// newlines are printed along with their
   340  	// subsequent indentation characters.
   341  	newLevel := p.level
   342  	p.level = p.lastLevel
   343  
   344  	for _, r := range hdocs {
   345  		p.line++
   346  		p.WriteByte('\n')
   347  		p.wantNewline, p.wantSpace = false, false
   348  		if r.Op == DashHdoc && p.indentSpaces == 0 &&
   349  			!p.minify && p.tabsPrinter != nil {
   350  			if r.Hdoc != nil {
   351  				extra := extraIndenter{
   352  					bufWriter:   p.bufWriter,
   353  					baseIndent:  int(p.level + 1),
   354  					firstIndent: -1,
   355  				}
   356  				*p.tabsPrinter = Printer{
   357  					bufWriter: &extra,
   358  				}
   359  				p.tabsPrinter.line = r.Hdoc.Pos().Line()
   360  				p.tabsPrinter.word(r.Hdoc)
   361  				p.indent()
   362  				p.line = r.Hdoc.End().Line()
   363  			} else {
   364  				p.indent()
   365  			}
   366  		} else if r.Hdoc != nil {
   367  			p.word(r.Hdoc)
   368  			p.line = r.Hdoc.End().Line()
   369  		}
   370  		p.unquotedWord(r.Word)
   371  		p.wantSpace = false
   372  	}
   373  	p.level = newLevel
   374  	p.pendingComments = coms
   375  }
   376  
   377  func (p *Printer) newlines(pos Pos) {
   378  	if p.firstLine && len(p.pendingComments) == 0 {
   379  		p.firstLine = false
   380  		return // no empty lines at the top
   381  	}
   382  	if !p.wantNewline && pos.Line() <= p.line {
   383  		return
   384  	}
   385  	p.newline(pos)
   386  	if pos.Line() > p.line {
   387  		if !p.minify {
   388  			// preserve single empty lines
   389  			p.WriteByte('\n')
   390  		}
   391  		p.line++
   392  	}
   393  	p.indent()
   394  }
   395  
   396  func (p *Printer) rightParen(pos Pos) {
   397  	if !p.minify {
   398  		p.newlines(pos)
   399  	}
   400  	p.WriteByte(')')
   401  	p.wantSpace = true
   402  }
   403  
   404  func (p *Printer) semiRsrv(s string, pos Pos) {
   405  	if p.wantNewline || pos.Line() > p.line {
   406  		p.newlines(pos)
   407  	} else {
   408  		if !p.wroteSemi {
   409  			p.WriteByte(';')
   410  		}
   411  		if !p.minify {
   412  			p.spacePad(pos)
   413  		}
   414  	}
   415  	p.WriteString(s)
   416  	p.wantSpace = true
   417  }
   418  
   419  func (p *Printer) flushComments() {
   420  	for i, c := range p.pendingComments {
   421  		p.firstLine = false
   422  		// We can't call any of the newline methods, as they call this
   423  		// function and we'd recurse forever.
   424  		cline := c.Hash.Line()
   425  		switch {
   426  		case i > 0, cline > p.line && p.line > 0:
   427  			p.WriteByte('\n')
   428  			if cline > p.line+1 {
   429  				p.WriteByte('\n')
   430  			}
   431  			p.indent()
   432  		case p.wantSpace:
   433  			if p.keepPadding {
   434  				p.spacePad(c.Pos())
   435  			} else {
   436  				p.spaces(p.commentPadding + 1)
   437  			}
   438  		}
   439  		// don't go back one line, which may happen in some edge cases
   440  		if p.line < cline {
   441  			p.line = cline
   442  		}
   443  		p.WriteByte('#')
   444  		p.WriteString(strings.TrimRightFunc(c.Text, unicode.IsSpace))
   445  		p.wantNewline = true
   446  	}
   447  	p.pendingComments = nil
   448  }
   449  
   450  func (p *Printer) comments(comments ...Comment) {
   451  	if p.minify {
   452  		return
   453  	}
   454  	p.pendingComments = append(p.pendingComments, comments...)
   455  }
   456  
   457  func (p *Printer) wordParts(wps []WordPart) {
   458  	for i, n := range wps {
   459  		var next WordPart
   460  		if i+1 < len(wps) {
   461  			next = wps[i+1]
   462  		}
   463  		p.wordPart(n, next)
   464  	}
   465  }
   466  
   467  func (p *Printer) wordPart(wp, next WordPart) {
   468  	switch x := wp.(type) {
   469  	case *Lit:
   470  		p.WriteString(x.Value)
   471  	case *SglQuoted:
   472  		if x.Dollar {
   473  			p.WriteByte('$')
   474  		}
   475  		p.WriteByte('\'')
   476  		p.WriteString(x.Value)
   477  		p.WriteByte('\'')
   478  		p.line = x.End().Line()
   479  	case *DblQuoted:
   480  		p.dblQuoted(x)
   481  	case *CmdSubst:
   482  		p.line = x.Pos().Line()
   483  		switch {
   484  		case x.TempFile:
   485  			p.WriteString("${")
   486  			p.wantSpace = true
   487  			p.nestedStmts(x.StmtList, x.Right)
   488  			p.wantSpace = false
   489  			p.semiRsrv("}", x.Right)
   490  		case x.ReplyVar:
   491  			p.WriteString("${|")
   492  			p.nestedStmts(x.StmtList, x.Right)
   493  			p.wantSpace = false
   494  			p.semiRsrv("}", x.Right)
   495  		default:
   496  			p.WriteString("$(")
   497  			p.wantSpace = len(x.Stmts) > 0 && startsWithLparen(x.Stmts[0])
   498  			p.nestedStmts(x.StmtList, x.Right)
   499  			p.rightParen(x.Right)
   500  		}
   501  	case *ParamExp:
   502  		litCont := ";"
   503  		if nextLit, ok := next.(*Lit); ok && nextLit.Value != "" {
   504  			litCont = nextLit.Value[:1]
   505  		}
   506  		name := x.Param.Value
   507  		switch {
   508  		case !p.minify:
   509  		case x.Excl, x.Length, x.Width:
   510  		case x.Index != nil, x.Slice != nil:
   511  		case x.Repl != nil, x.Exp != nil:
   512  		case len(name) > 1 && !ValidName(name): // ${10}
   513  		case ValidName(name + litCont): // ${var}cont
   514  		default:
   515  			x2 := *x
   516  			x2.Short = true
   517  			p.paramExp(&x2)
   518  			return
   519  		}
   520  		p.paramExp(x)
   521  	case *ArithmExp:
   522  		p.WriteString("$((")
   523  		if x.Unsigned {
   524  			p.WriteString("# ")
   525  		}
   526  		p.arithmExpr(x.X, false, false)
   527  		p.WriteString("))")
   528  	case *ExtGlob:
   529  		p.WriteString(x.Op.String())
   530  		p.WriteString(x.Pattern.Value)
   531  		p.WriteByte(')')
   532  	case *ProcSubst:
   533  		// avoid conflict with << and others
   534  		if p.wantSpace {
   535  			p.space()
   536  		}
   537  		p.WriteString(x.Op.String())
   538  		p.nestedStmts(x.StmtList, x.Rparen)
   539  		p.rightParen(x.Rparen)
   540  	}
   541  }
   542  
   543  func (p *Printer) dblQuoted(dq *DblQuoted) {
   544  	if dq.Dollar {
   545  		p.WriteByte('$')
   546  	}
   547  	p.WriteByte('"')
   548  	if len(dq.Parts) > 0 {
   549  		p.wordParts(dq.Parts)
   550  		p.line = dq.Parts[len(dq.Parts)-1].End().Line()
   551  	}
   552  	p.WriteByte('"')
   553  }
   554  
   555  func (p *Printer) wroteIndex(index ArithmExpr) bool {
   556  	if index == nil {
   557  		return false
   558  	}
   559  	p.WriteByte('[')
   560  	p.arithmExpr(index, false, false)
   561  	p.WriteByte(']')
   562  	return true
   563  }
   564  
   565  func (p *Printer) paramExp(pe *ParamExp) {
   566  	if pe.nakedIndex() { // arr[x]
   567  		p.WriteString(pe.Param.Value)
   568  		p.wroteIndex(pe.Index)
   569  		return
   570  	}
   571  	if pe.Short { // $var
   572  		p.WriteByte('$')
   573  		p.WriteString(pe.Param.Value)
   574  		return
   575  	}
   576  	// ${var...}
   577  	p.WriteString("${")
   578  	switch {
   579  	case pe.Length:
   580  		p.WriteByte('#')
   581  	case pe.Width:
   582  		p.WriteByte('%')
   583  	case pe.Excl:
   584  		p.WriteByte('!')
   585  	}
   586  	p.WriteString(pe.Param.Value)
   587  	p.wroteIndex(pe.Index)
   588  	switch {
   589  	case pe.Slice != nil:
   590  		p.WriteByte(':')
   591  		p.arithmExpr(pe.Slice.Offset, true, true)
   592  		if pe.Slice.Length != nil {
   593  			p.WriteByte(':')
   594  			p.arithmExpr(pe.Slice.Length, true, false)
   595  		}
   596  	case pe.Repl != nil:
   597  		if pe.Repl.All {
   598  			p.WriteByte('/')
   599  		}
   600  		p.WriteByte('/')
   601  		if pe.Repl.Orig != nil {
   602  			p.word(pe.Repl.Orig)
   603  		}
   604  		p.WriteByte('/')
   605  		if pe.Repl.With != nil {
   606  			p.word(pe.Repl.With)
   607  		}
   608  	case pe.Names != 0:
   609  		p.WriteString(pe.Names.String())
   610  	case pe.Exp != nil:
   611  		p.WriteString(pe.Exp.Op.String())
   612  		if pe.Exp.Word != nil {
   613  			p.word(pe.Exp.Word)
   614  		}
   615  	}
   616  	p.WriteByte('}')
   617  }
   618  
   619  func (p *Printer) loop(loop Loop) {
   620  	switch x := loop.(type) {
   621  	case *WordIter:
   622  		p.WriteString(x.Name.Value)
   623  		if x.InPos.IsValid() {
   624  			p.spacedString(" in", Pos{})
   625  			p.wordJoin(x.Items)
   626  		}
   627  	case *CStyleLoop:
   628  		p.WriteString("((")
   629  		if x.Init == nil {
   630  			p.space()
   631  		}
   632  		p.arithmExpr(x.Init, false, false)
   633  		p.WriteString("; ")
   634  		p.arithmExpr(x.Cond, false, false)
   635  		p.WriteString("; ")
   636  		p.arithmExpr(x.Post, false, false)
   637  		p.WriteString("))")
   638  	}
   639  }
   640  
   641  func (p *Printer) arithmExpr(expr ArithmExpr, compact, spacePlusMinus bool) {
   642  	if p.minify {
   643  		compact = true
   644  	}
   645  	switch x := expr.(type) {
   646  	case *Word:
   647  		p.word(x)
   648  	case *BinaryArithm:
   649  		if compact {
   650  			p.arithmExpr(x.X, compact, spacePlusMinus)
   651  			p.WriteString(x.Op.String())
   652  			p.arithmExpr(x.Y, compact, false)
   653  		} else {
   654  			p.arithmExpr(x.X, compact, spacePlusMinus)
   655  			if x.Op != Comma {
   656  				p.space()
   657  			}
   658  			p.WriteString(x.Op.String())
   659  			p.space()
   660  			p.arithmExpr(x.Y, compact, false)
   661  		}
   662  	case *UnaryArithm:
   663  		if x.Post {
   664  			p.arithmExpr(x.X, compact, spacePlusMinus)
   665  			p.WriteString(x.Op.String())
   666  		} else {
   667  			if spacePlusMinus {
   668  				switch x.Op {
   669  				case Plus, Minus:
   670  					p.space()
   671  				}
   672  			}
   673  			p.WriteString(x.Op.String())
   674  			p.arithmExpr(x.X, compact, false)
   675  		}
   676  	case *ParenArithm:
   677  		p.WriteByte('(')
   678  		p.arithmExpr(x.X, false, false)
   679  		p.WriteByte(')')
   680  	}
   681  }
   682  
   683  func (p *Printer) testExpr(expr TestExpr) {
   684  	switch x := expr.(type) {
   685  	case *Word:
   686  		p.word(x)
   687  	case *BinaryTest:
   688  		p.testExpr(x.X)
   689  		p.space()
   690  		p.WriteString(x.Op.String())
   691  		p.space()
   692  		p.testExpr(x.Y)
   693  	case *UnaryTest:
   694  		p.WriteString(x.Op.String())
   695  		p.space()
   696  		p.testExpr(x.X)
   697  	case *ParenTest:
   698  		p.WriteByte('(')
   699  		p.testExpr(x.X)
   700  		p.WriteByte(')')
   701  	}
   702  }
   703  
   704  func (p *Printer) word(w *Word) {
   705  	p.wordParts(w.Parts)
   706  	p.wantSpace = true
   707  }
   708  
   709  func (p *Printer) unquotedWord(w *Word) {
   710  	for _, wp := range w.Parts {
   711  		switch x := wp.(type) {
   712  		case *SglQuoted:
   713  			p.WriteString(x.Value)
   714  		case *DblQuoted:
   715  			p.wordParts(x.Parts)
   716  		case *Lit:
   717  			for i := 0; i < len(x.Value); i++ {
   718  				if b := x.Value[i]; b == '\\' {
   719  					if i++; i < len(x.Value) {
   720  						p.WriteByte(x.Value[i])
   721  					}
   722  				} else {
   723  					p.WriteByte(b)
   724  				}
   725  			}
   726  		}
   727  	}
   728  }
   729  
   730  func (p *Printer) wordJoin(ws []*Word) {
   731  	anyNewline := false
   732  	for _, w := range ws {
   733  		if pos := w.Pos(); pos.Line() > p.line {
   734  			if !anyNewline {
   735  				p.incLevel()
   736  				anyNewline = true
   737  			}
   738  			p.bslashNewl()
   739  		} else {
   740  			p.spacePad(w.Pos())
   741  		}
   742  		p.word(w)
   743  	}
   744  	if anyNewline {
   745  		p.decLevel()
   746  	}
   747  }
   748  
   749  func (p *Printer) casePatternJoin(pats []*Word) {
   750  	anyNewline := false
   751  	for i, w := range pats {
   752  		if i > 0 {
   753  			p.spacedToken("|", Pos{})
   754  		}
   755  		if pos := w.Pos(); pos.Line() > p.line {
   756  			if !anyNewline {
   757  				p.incLevel()
   758  				anyNewline = true
   759  			}
   760  			p.bslashNewl()
   761  		} else {
   762  			p.spacePad(w.Pos())
   763  		}
   764  		p.word(w)
   765  	}
   766  	if anyNewline {
   767  		p.decLevel()
   768  	}
   769  }
   770  
   771  func (p *Printer) elemJoin(elems []*ArrayElem, last []Comment) {
   772  	p.incLevel()
   773  	for _, el := range elems {
   774  		var left []Comment
   775  		for _, c := range el.Comments {
   776  			if c.Pos().After(el.Pos()) {
   777  				left = append(left, c)
   778  				break
   779  			}
   780  			p.comments(c)
   781  		}
   782  		if el.Pos().Line() > p.line {
   783  			p.newline(el.Pos())
   784  			p.indent()
   785  		} else if p.wantSpace {
   786  			p.space()
   787  		}
   788  		if p.wroteIndex(el.Index) {
   789  			p.WriteByte('=')
   790  		}
   791  		if el.Value != nil {
   792  			p.word(el.Value)
   793  		}
   794  		p.comments(left...)
   795  	}
   796  	if len(last) > 0 {
   797  		p.comments(last...)
   798  		p.flushComments()
   799  	}
   800  	p.decLevel()
   801  }
   802  
   803  func (p *Printer) stmt(s *Stmt) {
   804  	p.wroteSemi = false
   805  	if s.Negated {
   806  		p.spacedString("!", s.Pos())
   807  	}
   808  	var startRedirs int
   809  	if s.Cmd != nil {
   810  		startRedirs = p.command(s.Cmd, s.Redirs)
   811  	}
   812  	p.incLevel()
   813  	for _, r := range s.Redirs[startRedirs:] {
   814  		if r.OpPos.Line() > p.line {
   815  			p.bslashNewl()
   816  		}
   817  		if p.wantSpace {
   818  			p.spacePad(r.Pos())
   819  		}
   820  		if r.N != nil {
   821  			p.WriteString(r.N.Value)
   822  		}
   823  		p.WriteString(r.Op.String())
   824  		if p.spaceRedirects && (r.Op != DplIn && r.Op != DplOut) {
   825  			p.space()
   826  		} else {
   827  			p.wantSpace = true
   828  		}
   829  		p.word(r.Word)
   830  		if r.Op == Hdoc || r.Op == DashHdoc {
   831  			p.pendingHdocs = append(p.pendingHdocs, r)
   832  		}
   833  	}
   834  	switch {
   835  	case s.Semicolon.IsValid() && s.Semicolon.Line() > p.line:
   836  		p.bslashNewl()
   837  		p.WriteByte(';')
   838  		p.wroteSemi = true
   839  	case s.Background:
   840  		if !p.minify {
   841  			p.space()
   842  		}
   843  		p.WriteString("&")
   844  	case s.Coprocess:
   845  		if !p.minify {
   846  			p.space()
   847  		}
   848  		p.WriteString("|&")
   849  	}
   850  	p.decLevel()
   851  }
   852  
   853  func (p *Printer) command(cmd Command, redirs []*Redirect) (startRedirs int) {
   854  	p.spacePad(cmd.Pos())
   855  	switch x := cmd.(type) {
   856  	case *CallExpr:
   857  		p.assigns(x.Assigns)
   858  		if len(x.Args) <= 1 {
   859  			p.wordJoin(x.Args)
   860  			return 0
   861  		}
   862  		p.wordJoin(x.Args[:1])
   863  		for _, r := range redirs {
   864  			if r.Pos().After(x.Args[1].Pos()) || r.Op == Hdoc || r.Op == DashHdoc {
   865  				break
   866  			}
   867  			if p.wantSpace {
   868  				p.spacePad(r.Pos())
   869  			}
   870  			if r.N != nil {
   871  				p.WriteString(r.N.Value)
   872  			}
   873  			p.WriteString(r.Op.String())
   874  			if p.spaceRedirects && (r.Op != DplIn && r.Op != DplOut) {
   875  				p.space()
   876  			} else {
   877  				p.wantSpace = true
   878  			}
   879  			p.word(r.Word)
   880  			startRedirs++
   881  		}
   882  		p.wordJoin(x.Args[1:])
   883  	case *Block:
   884  		p.WriteByte('{')
   885  		p.wantSpace = true
   886  		p.nestedStmts(x.StmtList, x.Rbrace)
   887  		p.semiRsrv("}", x.Rbrace)
   888  	case *IfClause:
   889  		p.ifClause(x, false)
   890  	case *Subshell:
   891  		p.WriteByte('(')
   892  		p.wantSpace = len(x.Stmts) > 0 && startsWithLparen(x.Stmts[0])
   893  		p.spacePad(x.StmtList.pos())
   894  		p.nestedStmts(x.StmtList, x.Rparen)
   895  		p.wantSpace = false
   896  		p.spacePad(x.Rparen)
   897  		p.rightParen(x.Rparen)
   898  	case *WhileClause:
   899  		if x.Until {
   900  			p.spacedString("until", x.Pos())
   901  		} else {
   902  			p.spacedString("while", x.Pos())
   903  		}
   904  		p.nestedStmts(x.Cond, Pos{})
   905  		p.semiOrNewl("do", x.DoPos)
   906  		p.nestedStmts(x.Do, x.DonePos)
   907  		p.semiRsrv("done", x.DonePos)
   908  	case *ForClause:
   909  		if x.Select {
   910  			p.WriteString("select ")
   911  		} else {
   912  			p.WriteString("for ")
   913  		}
   914  		p.loop(x.Loop)
   915  		p.semiOrNewl("do", x.DoPos)
   916  		p.nestedStmts(x.Do, x.DonePos)
   917  		p.semiRsrv("done", x.DonePos)
   918  	case *BinaryCmd:
   919  		p.stmt(x.X)
   920  		if p.minify || x.Y.Pos().Line() <= p.line {
   921  			// leave p.nestedBinary untouched
   922  			p.spacedToken(x.Op.String(), x.OpPos)
   923  			p.line = x.Y.Pos().Line()
   924  			p.stmt(x.Y)
   925  			break
   926  		}
   927  		indent := !p.nestedBinary
   928  		if indent {
   929  			p.incLevel()
   930  		}
   931  		if p.binNextLine {
   932  			if len(p.pendingHdocs) == 0 {
   933  				p.bslashNewl()
   934  			}
   935  			p.spacedToken(x.Op.String(), x.OpPos)
   936  			if len(x.Y.Comments) > 0 {
   937  				p.wantSpace = false
   938  				p.newline(Pos{})
   939  				p.indent()
   940  				p.comments(x.Y.Comments...)
   941  				p.newline(Pos{})
   942  				p.indent()
   943  			}
   944  		} else {
   945  			p.spacedToken(x.Op.String(), x.OpPos)
   946  			p.line = x.OpPos.Line()
   947  			p.comments(x.Y.Comments...)
   948  			p.newline(Pos{})
   949  			p.indent()
   950  		}
   951  		p.line = x.Y.Pos().Line()
   952  		_, p.nestedBinary = x.Y.Cmd.(*BinaryCmd)
   953  		p.stmt(x.Y)
   954  		if indent {
   955  			p.decLevel()
   956  		}
   957  		p.nestedBinary = false
   958  	case *FuncDecl:
   959  		if x.RsrvWord {
   960  			p.WriteString("function ")
   961  		}
   962  		p.WriteString(x.Name.Value)
   963  		p.WriteString("()")
   964  		if !p.minify {
   965  			p.space()
   966  		}
   967  		p.line = x.Body.Pos().Line()
   968  		p.comments(x.Body.Comments...)
   969  		p.stmt(x.Body)
   970  	case *CaseClause:
   971  		p.WriteString("case ")
   972  		p.word(x.Word)
   973  		p.WriteString(" in")
   974  		if p.swtCaseIndent {
   975  			p.incLevel()
   976  		}
   977  		for i, ci := range x.Items {
   978  			var last []Comment
   979  			for i, c := range ci.Comments {
   980  				if c.Pos().After(ci.Pos()) {
   981  					last = ci.Comments[i:]
   982  					break
   983  				}
   984  				p.comments(c)
   985  			}
   986  			p.newlines(ci.Pos())
   987  			p.casePatternJoin(ci.Patterns)
   988  			p.WriteByte(')')
   989  			p.wantSpace = !p.minify
   990  			sep := len(ci.Stmts) > 1 || ci.StmtList.pos().Line() > p.line ||
   991  				(!ci.StmtList.empty() && ci.OpPos.Line() > ci.StmtList.end().Line())
   992  			p.nestedStmts(ci.StmtList, ci.OpPos)
   993  			p.level++
   994  			if !p.minify || i != len(x.Items)-1 {
   995  				if sep {
   996  					p.newlines(ci.OpPos)
   997  					p.wantNewline = true
   998  				}
   999  				p.spacedToken(ci.Op.String(), ci.OpPos)
  1000  				// avoid ; directly after tokens like ;;
  1001  				p.wroteSemi = true
  1002  			}
  1003  			p.comments(last...)
  1004  			p.flushComments()
  1005  			p.level--
  1006  		}
  1007  		p.comments(x.Last...)
  1008  		if p.swtCaseIndent {
  1009  			p.flushComments()
  1010  			p.decLevel()
  1011  		}
  1012  		p.semiRsrv("esac", x.Esac)
  1013  	case *ArithmCmd:
  1014  		p.WriteString("((")
  1015  		if x.Unsigned {
  1016  			p.WriteString("# ")
  1017  		}
  1018  		p.arithmExpr(x.X, false, false)
  1019  		p.WriteString("))")
  1020  	case *TestClause:
  1021  		p.WriteString("[[ ")
  1022  		p.testExpr(x.X)
  1023  		p.spacedString("]]", x.Right)
  1024  	case *DeclClause:
  1025  		p.spacedString(x.Variant.Value, x.Pos())
  1026  		for _, w := range x.Opts {
  1027  			p.space()
  1028  			p.word(w)
  1029  		}
  1030  		p.assigns(x.Assigns)
  1031  	case *TimeClause:
  1032  		p.spacedString("time", x.Pos())
  1033  		if x.PosixFormat {
  1034  			p.spacedString("-p", x.Pos())
  1035  		}
  1036  		if x.Stmt != nil {
  1037  			p.stmt(x.Stmt)
  1038  		}
  1039  	case *CoprocClause:
  1040  		p.spacedString("coproc", x.Pos())
  1041  		if x.Name != nil {
  1042  			p.space()
  1043  			p.WriteString(x.Name.Value)
  1044  		}
  1045  		p.space()
  1046  		p.stmt(x.Stmt)
  1047  	case *LetClause:
  1048  		p.spacedString("let", x.Pos())
  1049  		for _, n := range x.Exprs {
  1050  			p.space()
  1051  			p.arithmExpr(n, true, false)
  1052  		}
  1053  	}
  1054  	return startRedirs
  1055  }
  1056  
  1057  func (p *Printer) ifClause(ic *IfClause, elif bool) {
  1058  	if !elif {
  1059  		p.spacedString("if", ic.Pos())
  1060  	}
  1061  	p.nestedStmts(ic.Cond, Pos{})
  1062  	p.semiOrNewl("then", ic.ThenPos)
  1063  	p.nestedStmts(ic.Then, ic.bodyEndPos())
  1064  
  1065  	var left []Comment
  1066  	for _, c := range ic.ElseComments {
  1067  		if c.Pos().After(ic.ElsePos) {
  1068  			left = append(left, c)
  1069  			break
  1070  		}
  1071  		p.comments(c)
  1072  	}
  1073  	if ic.FollowedByElif() {
  1074  		s := ic.Else.Stmts[0]
  1075  		p.comments(s.Comments...)
  1076  		p.semiRsrv("elif", ic.ElsePos)
  1077  		p.ifClause(s.Cmd.(*IfClause), true)
  1078  		return
  1079  	}
  1080  	if !ic.Else.empty() {
  1081  		p.semiRsrv("else", ic.ElsePos)
  1082  		p.comments(left...)
  1083  		p.nestedStmts(ic.Else, ic.FiPos)
  1084  	} else if ic.ElsePos.IsValid() {
  1085  		p.line = ic.ElsePos.Line()
  1086  	}
  1087  	p.comments(ic.FiComments...)
  1088  	p.semiRsrv("fi", ic.FiPos)
  1089  }
  1090  
  1091  func startsWithLparen(s *Stmt) bool {
  1092  	switch x := s.Cmd.(type) {
  1093  	case *Subshell:
  1094  		return true
  1095  	case *BinaryCmd:
  1096  		return startsWithLparen(x.X)
  1097  	}
  1098  	return false
  1099  }
  1100  
  1101  func (p *Printer) hasInline(s *Stmt) bool {
  1102  	for _, c := range s.Comments {
  1103  		if c.Pos().Line() == s.End().Line() {
  1104  			return true
  1105  		}
  1106  	}
  1107  	return false
  1108  }
  1109  
  1110  func (p *Printer) stmtList(sl StmtList) {
  1111  	sep := p.wantNewline ||
  1112  		(len(sl.Stmts) > 0 && sl.Stmts[0].Pos().Line() > p.line)
  1113  	inlineIndent := 0
  1114  	lastIndentedLine := uint(0)
  1115  	for i, s := range sl.Stmts {
  1116  		pos := s.Pos()
  1117  		var midComs, endComs []Comment
  1118  		for _, c := range s.Comments {
  1119  			if c.End().After(s.End()) {
  1120  				endComs = append(endComs, c)
  1121  				break
  1122  			}
  1123  			if c.Pos().After(s.Pos()) {
  1124  				midComs = append(midComs, c)
  1125  				continue
  1126  			}
  1127  			p.comments(c)
  1128  		}
  1129  		if !p.minify || p.wantSpace {
  1130  			p.newlines(pos)
  1131  		}
  1132  		p.line = pos.Line()
  1133  		if !p.hasInline(s) {
  1134  			inlineIndent = 0
  1135  			p.commentPadding = 0
  1136  			p.comments(midComs...)
  1137  			p.stmt(s)
  1138  			p.wantNewline = true
  1139  			continue
  1140  		}
  1141  		p.comments(midComs...)
  1142  		p.stmt(s)
  1143  		if s.Pos().Line() > lastIndentedLine+1 {
  1144  			inlineIndent = 0
  1145  		}
  1146  		if inlineIndent == 0 {
  1147  			for _, s2 := range sl.Stmts[i:] {
  1148  				if !p.hasInline(s2) {
  1149  					break
  1150  				}
  1151  				if l := p.stmtCols(s2); l > inlineIndent {
  1152  					inlineIndent = l
  1153  				}
  1154  			}
  1155  		}
  1156  		if inlineIndent > 0 {
  1157  			if l := p.stmtCols(s); l > 0 {
  1158  				p.commentPadding = uint(inlineIndent - l)
  1159  			}
  1160  			lastIndentedLine = p.line
  1161  		}
  1162  		p.comments(endComs...)
  1163  		p.wantNewline = true
  1164  	}
  1165  	if len(sl.Stmts) == 1 && !sep {
  1166  		p.wantNewline = false
  1167  	}
  1168  	p.comments(sl.Last...)
  1169  }
  1170  
  1171  type byteCounter int
  1172  
  1173  func (c *byteCounter) WriteByte(b byte) error {
  1174  	switch {
  1175  	case *c < 0:
  1176  	case b == '\n':
  1177  		*c = -1
  1178  	default:
  1179  		*c++
  1180  	}
  1181  	return nil
  1182  }
  1183  func (c *byteCounter) Write(p []byte) (int, error) {
  1184  	return c.WriteString(string(p))
  1185  }
  1186  func (c *byteCounter) WriteString(s string) (int, error) {
  1187  	switch {
  1188  	case *c < 0:
  1189  	case strings.Contains(s, "\n"):
  1190  		*c = -1
  1191  	default:
  1192  		*c += byteCounter(len(s))
  1193  	}
  1194  	return 0, nil
  1195  }
  1196  func (c *byteCounter) Reset(io.Writer) { *c = 0 }
  1197  func (c *byteCounter) Flush() error    { return nil }
  1198  
  1199  // extraIndenter ensures that all lines in a '<<-' heredoc body have at least
  1200  // baseIndent leading tabs. Those that had more tab indentation than the first
  1201  // heredoc line will keep that relative indentation.
  1202  type extraIndenter struct {
  1203  	bufWriter
  1204  	baseIndent int
  1205  
  1206  	firstIndent int
  1207  	firstChange int
  1208  	curLine     []byte
  1209  }
  1210  
  1211  func (e *extraIndenter) WriteByte(b byte) error {
  1212  	e.curLine = append(e.curLine, b)
  1213  	if b != '\n' {
  1214  		return nil
  1215  	}
  1216  	trimmed := bytes.TrimLeft(e.curLine, "\t")
  1217  	lineIndent := len(e.curLine) - len(trimmed)
  1218  	if e.firstIndent < 0 {
  1219  		e.firstIndent = lineIndent
  1220  		e.firstChange = e.baseIndent - lineIndent
  1221  		lineIndent = e.baseIndent
  1222  	} else {
  1223  		if lineIndent < e.firstIndent {
  1224  			lineIndent = e.firstIndent
  1225  		} else {
  1226  			lineIndent += e.firstChange
  1227  		}
  1228  	}
  1229  	for i := 0; i < lineIndent; i++ {
  1230  		e.bufWriter.WriteByte('\t')
  1231  	}
  1232  	e.bufWriter.Write(trimmed)
  1233  	e.curLine = e.curLine[:0]
  1234  	return nil
  1235  }
  1236  
  1237  func (e *extraIndenter) WriteString(s string) (int, error) {
  1238  	for i := 0; i < len(s); i++ {
  1239  		e.WriteByte(s[i])
  1240  	}
  1241  	return len(s), nil
  1242  }
  1243  
  1244  // stmtCols reports the length that s will take when formatted in a
  1245  // single line. If it will span multiple lines, stmtCols will return -1.
  1246  func (p *Printer) stmtCols(s *Stmt) int {
  1247  	if p.lenPrinter == nil {
  1248  		return -1 // stmtCols call within stmtCols, bail
  1249  	}
  1250  	*p.lenPrinter = Printer{
  1251  		bufWriter: &p.lenCounter,
  1252  		line:      s.Pos().Line(),
  1253  	}
  1254  	p.lenPrinter.bufWriter.Reset(nil)
  1255  	p.lenPrinter.stmt(s)
  1256  	return int(p.lenCounter)
  1257  }
  1258  
  1259  func (p *Printer) nestedStmts(sl StmtList, closing Pos) {
  1260  	p.incLevel()
  1261  	switch {
  1262  	case len(sl.Stmts) > 1:
  1263  		// Force a newline if we find:
  1264  		//     { stmt; stmt; }
  1265  		p.wantNewline = true
  1266  	case closing.Line() > p.line && len(sl.Stmts) > 0 &&
  1267  		sl.end().Line() < closing.Line():
  1268  		// Force a newline if we find:
  1269  		//     { stmt
  1270  		//     }
  1271  		p.wantNewline = true
  1272  	case len(p.pendingComments) > 0 && len(sl.Stmts) > 0:
  1273  		// Force a newline if we find:
  1274  		//     for i in a b # stmt
  1275  		//     do foo; done
  1276  		p.wantNewline = true
  1277  	}
  1278  	p.stmtList(sl)
  1279  	if closing.IsValid() {
  1280  		p.flushComments()
  1281  	}
  1282  	p.decLevel()
  1283  }
  1284  
  1285  func (p *Printer) assigns(assigns []*Assign) {
  1286  	p.incLevel()
  1287  	for _, a := range assigns {
  1288  		if a.Pos().Line() > p.line {
  1289  			p.bslashNewl()
  1290  		} else {
  1291  			p.spacePad(a.Pos())
  1292  		}
  1293  		if a.Name != nil {
  1294  			p.WriteString(a.Name.Value)
  1295  			p.wroteIndex(a.Index)
  1296  			if a.Append {
  1297  				p.WriteByte('+')
  1298  			}
  1299  			if !a.Naked {
  1300  				p.WriteByte('=')
  1301  			}
  1302  		}
  1303  		if a.Value != nil {
  1304  			p.word(a.Value)
  1305  		} else if a.Array != nil {
  1306  			p.wantSpace = false
  1307  			p.WriteByte('(')
  1308  			p.elemJoin(a.Array.Elems, a.Array.Last)
  1309  			p.rightParen(a.Array.Rparen)
  1310  		}
  1311  		p.wantSpace = true
  1312  	}
  1313  	p.decLevel()
  1314  }