bash - format_print.go
		
     1  package bash
     2  
     3  import (
     4  	"strings"
     5  
     6  	"vimagination.zapto.org/parser"
     7  )
     8  
     9  func (a ArithmeticExpansion) printSource(w writer, v bool) {
    10  	if a.Expression {
    11  		w.WriteString("((")
    12  	} else {
    13  		w.WriteString("$((")
    14  	}
    15  
    16  	if len(a.WordsAndOperators) > 0 {
    17  		if v {
    18  			w.WriteString(" ")
    19  		}
    20  
    21  		a.WordsAndOperators[0].printSource(w, v)
    22  
    23  		for _, wo := range a.WordsAndOperators[1:] {
    24  			if v && !wo.operatorIsToken(parser.Token{Type: TokenPunctuator, Data: ";"}) {
    25  				w.WriteString(" ")
    26  			}
    27  
    28  			wo.printSource(w, v)
    29  		}
    30  
    31  		if v {
    32  			w.WriteString(" ")
    33  		}
    34  	}
    35  
    36  	w.WriteString("))")
    37  }
    38  
    39  func (a ArrayWord) printSource(w writer, v bool) {
    40  	if len(a.Comments[0]) > 0 {
    41  		a.Comments[0].printSource(w, true)
    42  	}
    43  
    44  	a.Word.printSource(w, v)
    45  
    46  	if len(a.Comments[1]) > 0 {
    47  		w.WriteString(" ")
    48  		a.Comments[1].printSource(w, false)
    49  	}
    50  }
    51  
    52  func (a Assignment) printSource(w writer, v bool) {
    53  	if a.Assignment == AssignmentAssign || a.Assignment == AssignmentAppend {
    54  		a.Identifier.printSource(w, v)
    55  		a.Assignment.printSource(w, v)
    56  
    57  		if a.Value != nil {
    58  			a.Value.printSource(w, v)
    59  		} else {
    60  			parens := 0
    61  
    62  			for _, e := range a.Expression {
    63  				if parens > 0 {
    64  					w.WriteString(" ")
    65  				}
    66  
    67  				e.printSource(w, v)
    68  
    69  				if v && e.Operator != nil {
    70  					if e.Operator.Token == (parser.Token{Type: TokenPunctuator, Data: "("}) {
    71  						parens++
    72  					} else if e.Operator.Token == (parser.Token{Type: TokenPunctuator, Data: ")"}) {
    73  						parens--
    74  					}
    75  				}
    76  			}
    77  		}
    78  	}
    79  }
    80  
    81  func (a AssignmentOrWord) printSource(w writer, v bool) {
    82  	if a.Assignment != nil {
    83  		a.Assignment.printSource(w, v)
    84  	} else if a.Word != nil {
    85  		a.Word.printSource(w, v)
    86  	}
    87  }
    88  
    89  func (b BraceExpansion) printSource(w writer, v bool) {
    90  	if b.BraceExpansionType == BraceExpansionWords && len(b.Words) > 1 || (b.BraceExpansionType == BraceExpansionSequence && (len(b.Words) == 2 || len(b.Words) == 3)) {
    91  		w.WriteString("{")
    92  
    93  		b.Words[0].printSource(w, v)
    94  
    95  		for _, wd := range b.Words[1:] {
    96  			b.BraceExpansionType.printSource(w, v)
    97  			wd.printSource(w, v)
    98  		}
    99  
   100  		w.WriteString("}")
   101  	}
   102  }
   103  
   104  func (b BraceWord) printSource(w writer, v bool) {
   105  	for _, wp := range b.Parts {
   106  		wp.printSource(w, v)
   107  	}
   108  }
   109  
   110  func (c CaseCompound) printSource(w writer, v bool) {
   111  	w.WriteString("case ")
   112  	c.Word.printSource(w, v)
   113  
   114  	if len(c.Comments[0]) > 0 {
   115  		w.WriteString(" ")
   116  		c.Comments[0].printSource(w, false)
   117  		w.WriteString("\nin")
   118  	} else {
   119  		w.WriteString(" in")
   120  	}
   121  
   122  	if len(c.Comments[1]) > 0 {
   123  		w.WriteString(" ")
   124  		c.Comments[1].printSource(w, false)
   125  	}
   126  
   127  	for _, m := range c.Matches {
   128  		w.WriteString("\n")
   129  		m.printSource(w, v)
   130  	}
   131  
   132  	if len(c.Comments[2]) > 0 {
   133  		w.WriteString("\n")
   134  		c.Comments[2].printSource(w, false)
   135  	}
   136  
   137  	w.WriteString("\nesac")
   138  }
   139  
   140  func (c Command) printSource(w writer, v bool) {
   141  	if len(c.Vars) > 0 {
   142  		c.Vars[0].printSource(w, v)
   143  
   144  		for _, vr := range c.Vars[1:] {
   145  			w.WriteString(" ")
   146  			vr.printSource(w, v)
   147  		}
   148  	}
   149  
   150  	if len(c.AssignmentsOrWords) > 0 {
   151  		if len(c.Vars) > 0 {
   152  			w.WriteString(" ")
   153  		}
   154  
   155  		c.AssignmentsOrWords[0].printSource(w, v)
   156  
   157  		for _, wd := range c.AssignmentsOrWords[1:] {
   158  			w.WriteString(" ")
   159  			wd.printSource(w, v)
   160  		}
   161  	}
   162  
   163  	if len(c.Redirections) > 0 {
   164  		if len(c.Vars) > 0 || len(c.AssignmentsOrWords) > 0 {
   165  			w.WriteString(" ")
   166  		}
   167  
   168  		c.Redirections[0].printSource(w, v)
   169  
   170  		for _, r := range c.Redirections[1:] {
   171  			w.WriteString(" ")
   172  			r.printSource(w, v)
   173  		}
   174  	}
   175  }
   176  
   177  func (c Command) printHeredoc(w writer, v bool) {
   178  	for _, r := range c.Redirections {
   179  		r.printHeredoc(w, v)
   180  	}
   181  }
   182  
   183  func (c Command) hasHeredoc() bool {
   184  	for _, r := range c.Redirections {
   185  		if r.isHeredoc() {
   186  			return true
   187  		}
   188  	}
   189  
   190  	return false
   191  }
   192  
   193  func (c CommandOrCompound) printSource(w writer, v bool) {
   194  	if c.Command != nil {
   195  		c.Command.printSource(w, v)
   196  	} else if c.Compound != nil {
   197  		c.Compound.printSource(w, v)
   198  	}
   199  }
   200  
   201  func (c CommandOrCompound) printHeredoc(w writer, v bool) {
   202  	if c.Command != nil {
   203  		c.Command.printHeredoc(w, v)
   204  	} else if c.Compound != nil {
   205  		c.Compound.printHeredoc(w, v)
   206  	}
   207  }
   208  
   209  func (c CommandOrCompound) hasHeredoc() bool {
   210  	if c.Command != nil && c.Command.hasHeredoc() {
   211  		return true
   212  	}
   213  
   214  	if c.Compound != nil && c.Compound.hasHeredoc() {
   215  		return true
   216  	}
   217  
   218  	return false
   219  }
   220  
   221  func (c CommandSubstitution) printSource(w writer, v bool) {
   222  	closing := ")"
   223  
   224  	switch c.SubstitutionType {
   225  	case SubstitutionNew:
   226  		w.WriteString("$(")
   227  	case SubstitutionBacktick:
   228  		closing = "`"
   229  
   230  		if c.Backtick != nil {
   231  			closing = c.Backtick.Data
   232  		}
   233  
   234  		w.WriteString(closing)
   235  
   236  		closing = c.Backtick.Data
   237  	case SubstitutionProcessInput:
   238  		w.WriteString("<(")
   239  	case SubstitutionProcessOutput:
   240  		w.WriteString(">(")
   241  	}
   242  
   243  	if c.Command.isMultiline(v) {
   244  		ip := w.Indent()
   245  
   246  		ip.WriteString("\n")
   247  		c.Command.printSource(ip, v)
   248  		w.WriteString("\n")
   249  	} else {
   250  		c.Command.printSourceEnd(w, v, false)
   251  	}
   252  
   253  	w.WriteString(closing)
   254  }
   255  
   256  func (c Compound) printSource(w writer, v bool) {
   257  	if c.IfCompound != nil {
   258  		c.IfCompound.printSource(w, v)
   259  	} else if c.CaseCompound != nil {
   260  		c.CaseCompound.printSource(w, v)
   261  	} else if c.LoopCompound != nil {
   262  		c.LoopCompound.printSource(w, v)
   263  	} else if c.ForCompound != nil {
   264  		c.ForCompound.printSource(w, v)
   265  	} else if c.SelectCompound != nil {
   266  		c.SelectCompound.printSource(w, v)
   267  	} else if c.GroupingCompound != nil {
   268  		c.GroupingCompound.printSource(w, v)
   269  	} else if c.TestCompound != nil {
   270  		c.TestCompound.printSource(w, v)
   271  	} else if c.ArithmeticCompound != nil {
   272  		c.ArithmeticCompound.printSource(w, v)
   273  	} else if c.FunctionCompound != nil {
   274  		c.FunctionCompound.printSource(w, v)
   275  	}
   276  
   277  	for _, r := range c.Redirections {
   278  		w.WriteString(" ")
   279  		r.printSource(w, v)
   280  	}
   281  }
   282  
   283  func (c Compound) printHeredoc(w writer, v bool) {
   284  	for _, r := range c.Redirections {
   285  		r.printHeredoc(w, v)
   286  	}
   287  }
   288  
   289  func (c Compound) hasHeredoc() bool {
   290  	for _, r := range c.Redirections {
   291  		if r.isHeredoc() {
   292  			return true
   293  		}
   294  	}
   295  
   296  	return false
   297  }
   298  
   299  func (f File) printSource(w writer, v bool) {
   300  	f.printSourceEnd(w, v, true)
   301  }
   302  
   303  func (f File) printSourceEnd(w writer, v, end bool) {
   304  	f.Comments[0].printSource(w, true)
   305  
   306  	topLevel := w.Underlying() == w
   307  
   308  	if len(f.Lines) > 0 {
   309  		if topLevel && len(f.Comments[0]) > 0 {
   310  			w.WriteString("\n")
   311  		}
   312  
   313  		f.Lines[0].printSourceEnd(w, v, end || len(f.Lines) > 1)
   314  
   315  		lastLine := lastTokenPos(f.Lines[0].Tokens)
   316  
   317  		for n, l := range f.Lines[1:] {
   318  			if firstTokenPos(l.Tokens) > lastLine+1 {
   319  				w.WriteString("\n")
   320  			}
   321  
   322  			w.WriteString("\n")
   323  			l.printSourceEnd(w, v, end || len(f.Lines) > n+1)
   324  
   325  			lastLine = lastTokenPos(l.Tokens)
   326  		}
   327  	}
   328  
   329  	if len(f.Comments[1]) > 0 {
   330  		w.WriteString("\n\n")
   331  		f.Comments[1].printSource(w, false)
   332  	}
   333  
   334  	if topLevel && end {
   335  		w.WriteString("\n")
   336  	}
   337  }
   338  
   339  func firstTokenPos(tk Tokens) (pos uint64) {
   340  	if len(tk) > 0 {
   341  		pos = tk[0].Line
   342  	}
   343  
   344  	return pos
   345  }
   346  
   347  func lastTokenPos(tk Tokens) (pos uint64) {
   348  	if len(tk) > 0 {
   349  		pos = tk[len(tk)-1].Line
   350  	}
   351  
   352  	return pos
   353  }
   354  
   355  func (f ForCompound) printSource(w writer, v bool) {
   356  	if f.ArithmeticExpansion != nil || f.Identifier != nil {
   357  		w.WriteString("for ")
   358  
   359  		if f.ArithmeticExpansion != nil {
   360  			f.ArithmeticExpansion.printSource(w, v)
   361  		} else {
   362  			w.WriteString(f.Identifier.Data)
   363  
   364  			if f.Words != nil {
   365  				w.WriteString(" ")
   366  				f.Comments[0].printSource(w, true)
   367  				w.WriteString("in")
   368  
   369  				for _, wd := range f.Words {
   370  					w.WriteString(" ")
   371  					wd.printSource(w, v)
   372  				}
   373  			}
   374  		}
   375  
   376  		ip := w.Indent()
   377  
   378  		ip.WriteString("; ")
   379  		f.Comments[1].printSource(ip, true)
   380  		ip.WriteString("do\n")
   381  		f.File.printSource(ip, v)
   382  		w.WriteString("\ndone")
   383  	}
   384  }
   385  
   386  func (f FunctionCompound) printSource(w writer, v bool) {
   387  	if f.Identifier != nil {
   388  		if f.HasKeyword {
   389  			w.WriteString("function ")
   390  		}
   391  
   392  		w.WriteString(f.Identifier.Data)
   393  		w.WriteString("() ")
   394  		f.Comments.printSource(w, true)
   395  		f.Body.printSource(w, v)
   396  	}
   397  }
   398  
   399  func (g GroupingCompound) printSource(w writer, v bool) {
   400  	if g.SubShell {
   401  		w.WriteString("(")
   402  	} else {
   403  		w.WriteString("{")
   404  	}
   405  
   406  	ip := w.Indent()
   407  	multiline := v || g.File.isMultiline(v)
   408  
   409  	if len(g.File.Comments[0]) > 0 || !multiline {
   410  		w.WriteString(" ")
   411  	} else {
   412  		ip.WriteString("\n")
   413  	}
   414  
   415  	g.File.printSource(ip, v)
   416  
   417  	if multiline {
   418  		w.WriteString("\n")
   419  	} else {
   420  		w.WriteString(" ")
   421  	}
   422  
   423  	if g.SubShell {
   424  		w.WriteString(")")
   425  	} else {
   426  		w.WriteString("}")
   427  	}
   428  }
   429  
   430  func (h Heredoc) printSource(w writer, v bool) {
   431  	w.WriteString("\n")
   432  
   433  	for _, p := range h.HeredocPartsOrWords {
   434  		p.printSource(w, v)
   435  	}
   436  }
   437  
   438  func (h HeredocPartOrWord) printSource(w writer, v bool) {
   439  	if h.HeredocPart != nil {
   440  		w.WriteString(h.HeredocPart.Data)
   441  	} else if h.Word != nil {
   442  		h.Word.printSource(w, v)
   443  	}
   444  }
   445  
   446  func (i IfCompound) printSource(w writer, v bool) {
   447  	w.WriteString("if ")
   448  	i.If.printSource(w, v)
   449  
   450  	for _, e := range i.ElIf {
   451  		w.WriteString("\nelif ")
   452  		e.printSource(w, v)
   453  	}
   454  
   455  	if i.Else != nil {
   456  		ip := w.Indent()
   457  
   458  		w.WriteString("\nelse")
   459  		ip.WriteString("\n")
   460  		i.Else.printSource(ip, v)
   461  	}
   462  
   463  	w.WriteString("\nfi")
   464  }
   465  
   466  func (l Line) printSource(w writer, v bool) {
   467  	l.printSourceEnd(w, v, true)
   468  }
   469  
   470  func (l Line) printSourceEnd(w writer, v, end bool) {
   471  	if len(l.Statements) > 0 {
   472  		end = end && !l.hasHeredoc()
   473  
   474  		l.Comments[0].printSource(w, true)
   475  		l.Statements[0].printSourceEnd(w, v, end || len(l.Statements) > 1)
   476  
   477  		for n, s := range l.Statements[1:] {
   478  			if v {
   479  				l.Statements[n].printHeredoc(w, v)
   480  				w.WriteString("\n")
   481  			} else {
   482  				w.WriteString(" ")
   483  			}
   484  
   485  			s.printSourceEnd(w, v, end || len(l.Statements) > n+1)
   486  		}
   487  
   488  		if len(l.Comments[1]) > 0 {
   489  			w.WriteString(" ")
   490  			l.Comments[1].printSource(w, false)
   491  		}
   492  
   493  		if v {
   494  			l.Statements[len(l.Statements)-1].printHeredoc(w, v)
   495  		} else {
   496  			for _, s := range l.Statements {
   497  				s.printHeredoc(w, v)
   498  			}
   499  		}
   500  	}
   501  }
   502  
   503  func (l Line) hasHeredoc() bool {
   504  	for _, s := range l.Statements {
   505  		if s.hasHeredoc() {
   506  			return true
   507  		}
   508  	}
   509  
   510  	return false
   511  }
   512  
   513  func (l LoopCompound) printSource(w writer, v bool) {
   514  	if l.Until {
   515  		w.WriteString("until ")
   516  	} else {
   517  		w.WriteString("while ")
   518  	}
   519  
   520  	l.Statement.printSource(w, v)
   521  
   522  	if l.Statement.endsWithGrouping() {
   523  		w.WriteString(";")
   524  	}
   525  
   526  	if len(l.Comments) > 0 {
   527  		w.WriteString(" ")
   528  		l.Comments.printSource(w, true)
   529  		w.WriteString("do")
   530  	} else {
   531  		w.WriteString(" do")
   532  	}
   533  
   534  	ip := w.Indent()
   535  
   536  	ip.WriteString("\n")
   537  	l.File.printSource(ip, v)
   538  	w.WriteString("\ndone")
   539  }
   540  
   541  func (p ParameterAssign) printSource(w writer, v bool) {
   542  	if p.Identifier != nil {
   543  		w.WriteString(p.Identifier.Data)
   544  
   545  		if len(p.Subscript) > 0 {
   546  			w.WriteString("[")
   547  			p.Subscript[0].printSource(w, v)
   548  
   549  			for _, s := range p.Subscript[1:] {
   550  				if v {
   551  					w.WriteString(" ")
   552  				}
   553  
   554  				s.printSource(w, v)
   555  			}
   556  
   557  			w.WriteString("]")
   558  		}
   559  	}
   560  }
   561  
   562  func (p ParameterExpansion) printSource(w writer, v bool) {
   563  	w.WriteString("${")
   564  
   565  	if p.Indirect || p.Type == ParameterPrefix || p.Type == ParameterPrefixSeperate {
   566  		w.WriteString("!")
   567  	} else if p.Type == ParameterLength {
   568  		w.WriteString("#")
   569  	}
   570  
   571  	p.Parameter.printSource(w, v)
   572  
   573  	if p.BraceWord != nil {
   574  		switch p.Type {
   575  		case ParameterSubstitution:
   576  			w.WriteString(":=")
   577  			p.BraceWord.printSource(w, v)
   578  		case ParameterAssignment:
   579  			w.WriteString(":?")
   580  			p.BraceWord.printSource(w, v)
   581  		case ParameterMessage:
   582  			w.WriteString(":+")
   583  			p.BraceWord.printSource(w, v)
   584  		case ParameterSetAssign:
   585  			w.WriteString(":-")
   586  			p.BraceWord.printSource(w, v)
   587  		case ParameterUnsetSubstitution:
   588  			w.WriteString("=")
   589  			p.BraceWord.printSource(w, v)
   590  		case ParameterUnsetAssignment:
   591  			w.WriteString("?")
   592  			p.BraceWord.printSource(w, v)
   593  		case ParameterUnsetMessage:
   594  			w.WriteString("+")
   595  			p.BraceWord.printSource(w, v)
   596  		case ParameterUnsetSetAssign:
   597  			w.WriteString("-")
   598  			p.BraceWord.printSource(w, v)
   599  		case ParameterRemoveStartShortest:
   600  			w.WriteString("#")
   601  			p.BraceWord.printSource(w, v)
   602  		case ParameterRemoveStartLongest:
   603  			w.WriteString("##")
   604  			p.BraceWord.printSource(w, v)
   605  		case ParameterRemoveEndShortest:
   606  			w.WriteString("%")
   607  			p.BraceWord.printSource(w, v)
   608  		case ParameterRemoveEndLongest:
   609  			w.WriteString("%%")
   610  			p.BraceWord.printSource(w, v)
   611  		}
   612  	} else if p.Pattern != nil {
   613  		isReplacement := false
   614  
   615  		switch p.Type {
   616  		case ParameterReplace:
   617  			isReplacement = true
   618  
   619  			w.WriteString("/")
   620  			w.WriteString(p.Pattern.Data)
   621  		case ParameterReplaceAll:
   622  			isReplacement = true
   623  
   624  			w.WriteString("//")
   625  			w.WriteString(p.Pattern.Data)
   626  		case ParameterReplaceStart:
   627  			isReplacement = true
   628  
   629  			w.WriteString("/#")
   630  			w.WriteString(p.Pattern.Data)
   631  		case ParameterReplaceEnd:
   632  			isReplacement = true
   633  
   634  			w.WriteString("/%")
   635  			w.WriteString(p.Pattern.Data)
   636  		case ParameterLowercaseFirstMatch:
   637  			w.WriteString(",")
   638  			w.WriteString(p.Pattern.Data)
   639  		case ParameterLowercaseAllMatches:
   640  			w.WriteString(",,")
   641  			w.WriteString(p.Pattern.Data)
   642  		case ParameterUppercaseFirstMatch:
   643  			w.WriteString("^")
   644  			w.WriteString(p.Pattern.Data)
   645  		case ParameterUppercaseAllMatches:
   646  			w.WriteString("^^")
   647  			w.WriteString(p.Pattern.Data)
   648  		}
   649  
   650  		if isReplacement && p.String != nil {
   651  			w.WriteString("/")
   652  			p.String.printSource(w, v)
   653  		}
   654  	} else if !p.Indirect {
   655  		switch p.Type {
   656  		case ParameterPrefix:
   657  			w.WriteString("*")
   658  		case ParameterPrefixSeperate:
   659  			w.WriteString("@")
   660  		}
   661  	}
   662  
   663  	switch p.Type {
   664  	case ParameterSubstring:
   665  		if p.SubstringStart != nil {
   666  			w.WriteString(":")
   667  
   668  			if len(p.SubstringStart.Parts) > 0 && p.SubstringStart.Parts[0].Part != nil && strings.HasPrefix(p.SubstringStart.Parts[0].Part.Data, "-") {
   669  				w.WriteString(" ")
   670  			}
   671  
   672  			p.SubstringStart.printSource(w, v)
   673  
   674  			if p.SubstringEnd != nil {
   675  				w.WriteString(":")
   676  				p.SubstringEnd.printSource(w, v)
   677  			}
   678  		}
   679  	case ParameterUppercase:
   680  		w.WriteString("@U")
   681  	case ParameterUppercaseFirst:
   682  		w.WriteString("@u")
   683  	case ParameterLowercase:
   684  		w.WriteString("@L")
   685  	case ParameterQuoted:
   686  		w.WriteString("@Q")
   687  	case ParameterEscaped:
   688  		w.WriteString("@E")
   689  	case ParameterPrompt:
   690  		w.WriteString("@P")
   691  	case ParameterDeclare:
   692  		w.WriteString("@A")
   693  	case ParameterQuotedArrays:
   694  		w.WriteString("@K")
   695  	case ParameterAttributes:
   696  		w.WriteString("@a")
   697  	case ParameterQuotedArraysSeperate:
   698  		w.WriteString("@k")
   699  	}
   700  
   701  	w.WriteString("}")
   702  }
   703  
   704  func (p Parameter) printSource(w writer, v bool) {
   705  	if p.Parameter != nil {
   706  		w.WriteString(p.Parameter.Data)
   707  
   708  		if p.Array != nil {
   709  			w.WriteString("[")
   710  
   711  			for _, a := range p.Array {
   712  				a.printSource(w, v)
   713  			}
   714  
   715  			w.WriteString("]")
   716  		}
   717  	}
   718  }
   719  
   720  func (p Pattern) printSource(w writer, v bool) {
   721  	for _, word := range p.Parts {
   722  		word.printSource(w, v)
   723  	}
   724  }
   725  
   726  func (p PatternLines) printSource(w writer, v bool) {
   727  	if len(p.Patterns) > 0 {
   728  		p.Comments.printSource(w, true)
   729  		p.Patterns[0].printSource(w, v)
   730  
   731  		for _, pattern := range p.Patterns[1:] {
   732  			w.WriteString("|")
   733  			pattern.printSource(w, v)
   734  		}
   735  
   736  		ip := w.Indent()
   737  
   738  		w.WriteString(")")
   739  		ip.WriteString("\n")
   740  
   741  		if len(p.Lines.Lines) > 0 {
   742  			p.Lines.printSource(ip, v)
   743  		} else {
   744  			ip.WriteString(";")
   745  		}
   746  
   747  		p.CaseTerminationType.printSource(ip, v)
   748  	}
   749  }
   750  
   751  func (p Pipeline) printSource(w writer, v bool) {
   752  	p.PipelineTime.printSource(w, v)
   753  
   754  	if p.Not {
   755  		w.WriteString("! ")
   756  	}
   757  
   758  	if p.Coproc {
   759  		w.WriteString("coproc ")
   760  
   761  		if p.CoprocIdentifier != nil {
   762  			w.WriteString(p.CoprocIdentifier.Data)
   763  			w.WriteString(" ")
   764  		}
   765  	}
   766  
   767  	p.CommandOrCompound.printSource(w, v)
   768  
   769  	if p.Pipeline != nil {
   770  		w.WriteString(" | ")
   771  		p.Pipeline.printSource(w, v)
   772  	}
   773  }
   774  
   775  func (p Pipeline) endsWithGrouping() bool {
   776  	if p.Pipeline != nil {
   777  		return p.Pipeline.endsWithGrouping()
   778  	}
   779  
   780  	return p.CommandOrCompound.Compound != nil && len(p.CommandOrCompound.Compound.Redirections) == 0 && (p.CommandOrCompound.Compound.GroupingCompound != nil || p.CommandOrCompound.Compound.FunctionCompound != nil && p.CommandOrCompound.Compound.FunctionCompound.Body.GroupingCompound != nil)
   781  }
   782  
   783  func (p Pipeline) printHeredoc(w writer, v bool) {
   784  	p.CommandOrCompound.printHeredoc(w, v)
   785  
   786  	if p.Pipeline != nil {
   787  		p.Pipeline.printHeredoc(w, v)
   788  	}
   789  }
   790  
   791  func (p Pipeline) hasHeredoc() bool {
   792  	if p.Pipeline != nil && p.Pipeline.hasHeredoc() {
   793  		return true
   794  	}
   795  
   796  	return p.CommandOrCompound.hasHeredoc()
   797  }
   798  
   799  func (r Redirection) printSource(w writer, v bool) {
   800  	if r.Redirector != nil {
   801  		if r.Input != nil {
   802  			w.WriteString(r.Input.Data)
   803  		}
   804  
   805  		w.WriteString(r.Redirector.Data)
   806  
   807  		if v && r.Redirector.Data != "<<" && r.Redirector.Data != "<<-" && r.Redirector.Data != ">&" || r.Output.firstPartIsProcessSubstitution() {
   808  			w.WriteString(" ")
   809  		}
   810  
   811  		r.Output.printSource(w, v)
   812  	}
   813  }
   814  
   815  func (r Redirection) printHeredoc(w writer, v bool) {
   816  	if r.Redirector != nil && r.Heredoc != nil && (r.Redirector.Data == "<<" || r.Redirector.Data == "<<-") {
   817  		if r.Redirector.Data == "<<" {
   818  			w = w.Underlying()
   819  		}
   820  
   821  		r.Heredoc.printSource(w, v)
   822  		r.Output.printSource(w, v)
   823  	}
   824  }
   825  
   826  func (s SelectCompound) printSource(w writer, v bool) {
   827  	if s.Identifier != nil {
   828  		w.WriteString("select ")
   829  		w.WriteString(s.Identifier.Data)
   830  
   831  		if s.Words != nil {
   832  			w.WriteString(" ")
   833  			s.Comments[0].printSource(w, true)
   834  			w.WriteString("in")
   835  
   836  			for _, wd := range s.Words {
   837  				w.WriteString(" ")
   838  				wd.printSource(w, v)
   839  			}
   840  		}
   841  
   842  		ip := w.Indent()
   843  
   844  		ip.WriteString("; ")
   845  		s.Comments[1].printSource(w, true)
   846  		ip.WriteString("do\n")
   847  		s.File.printSource(ip, v)
   848  		w.WriteString("\ndone")
   849  	}
   850  }
   851  
   852  func (s Statement) printSource(w writer, v bool) {
   853  	s.printSourceEnd(w, v, true)
   854  }
   855  
   856  func (s Statement) printSourceEnd(w writer, v, end bool) {
   857  	s.Pipeline.printSource(w, v)
   858  
   859  	if (s.LogicalOperator == LogicalOperatorAnd || s.LogicalOperator == LogicalOperatorOr) && s.Statement != nil {
   860  		w.WriteString(" ")
   861  		s.LogicalOperator.printSource(w, v)
   862  		w.WriteString(" ")
   863  		s.Statement.printSourceEnd(w, v, false)
   864  	}
   865  
   866  	if s.JobControl == JobControlBackground {
   867  		if v {
   868  			w.WriteString(" &")
   869  		} else {
   870  			w.WriteString("&")
   871  		}
   872  	} else if end && !s.endsWithGrouping() {
   873  		w.WriteString(";")
   874  	}
   875  }
   876  
   877  func (s Statement) endsWithGrouping() bool {
   878  	if s.Statement != nil {
   879  		return s.Statement.endsWithGrouping()
   880  	}
   881  
   882  	return s.Pipeline.endsWithGrouping()
   883  }
   884  
   885  func (s Statement) hasHeredoc() bool {
   886  	if s.Statement != nil && s.Statement.hasHeredoc() {
   887  		return true
   888  	}
   889  
   890  	return s.Pipeline.hasHeredoc()
   891  }
   892  
   893  func (s Statement) printHeredoc(w writer, v bool) {
   894  	s.Pipeline.printHeredoc(w, v)
   895  
   896  	if s.Statement != nil {
   897  		s.Statement.printHeredoc(w, v)
   898  	}
   899  }
   900  
   901  func (s String) printSource(w writer, v bool) {
   902  	for _, p := range s.WordsOrTokens {
   903  		p.printSource(w, v)
   904  	}
   905  }
   906  
   907  func (t TestCompound) printSource(w writer, v bool) {
   908  	w.WriteString("[[")
   909  
   910  	iw := w
   911  	multi := t.isMultiline(v)
   912  
   913  	if multi {
   914  		iw = w.Indent()
   915  		multi = true
   916  
   917  		if len(t.Comments[0]) > 0 {
   918  			w.WriteString(" ")
   919  			t.Comments[0].printSource(w, len(t.Tests.Comments[0]) > 0)
   920  		}
   921  
   922  		iw.WriteString("\n")
   923  	} else {
   924  		w.WriteString(" ")
   925  	}
   926  
   927  	t.Tests.printSource(iw, v)
   928  
   929  	if len(t.Comments[1]) > 0 {
   930  		if t.Tests.lastIsComment() {
   931  			w.WriteString("\n")
   932  		}
   933  
   934  		w.WriteString("\n")
   935  		t.Comments[1].printSource(w, true)
   936  	} else if multi {
   937  		w.WriteString("\n")
   938  	} else {
   939  		w.WriteString(" ")
   940  	}
   941  
   942  	w.WriteString("]]")
   943  }
   944  
   945  func (t Tests) printSource(w writer, v bool) {
   946  	t.Comments[0].printSource(w, true)
   947  
   948  	if t.Not {
   949  		w.WriteString("! ")
   950  
   951  		t.Comments[1].printSource(w, true)
   952  	}
   953  
   954  	if t.Parens != nil {
   955  		w.WriteString("(")
   956  
   957  		multi := t.Parens.isMultiline(v) || len(t.Comments[2]) > 0 || len(t.Comments[3]) > 0
   958  		iw := w
   959  
   960  		if multi {
   961  			iw = w.Indent()
   962  
   963  			if len(t.Comments[2]) > 0 {
   964  				w.WriteString(" ")
   965  				t.Comments[2].printSource(w, len(t.Parens.Comments[0]) > 0)
   966  			}
   967  
   968  			iw.WriteString("\n")
   969  		} else {
   970  			w.WriteString(" ")
   971  		}
   972  
   973  		t.Parens.printSource(iw, v)
   974  
   975  		if multi {
   976  			if len(t.Comments[3]) > 0 && len(t.Parens.Comments[4]) > 0 {
   977  				w.WriteString("\n\n")
   978  			} else {
   979  				w.WriteString("\n")
   980  			}
   981  		} else {
   982  			w.WriteString(" ")
   983  		}
   984  
   985  		t.Comments[3].printSource(w, true)
   986  		w.WriteString(")")
   987  	} else if t.Word != nil && t.Test == TestOperatorNone {
   988  		t.Word.printSource(w, v)
   989  	} else if t.Word != nil && t.Pattern != nil && t.Test >= TestOperatorStringsEqual {
   990  		t.Word.printSource(w, v)
   991  		w.WriteString(" ")
   992  		t.Test.printSource(w, v)
   993  		w.WriteString(" ")
   994  		t.Comments[2].printSource(w, true)
   995  		t.Pattern.printSource(w, v)
   996  	} else if t.Word != nil && t.Test >= TestOperatorFileExists && t.Test <= TestOperatorStringIsNonZero {
   997  		t.Test.printSource(w, v)
   998  		w.WriteString(" ")
   999  		t.Word.printSource(w, v)
  1000  	}
  1001  
  1002  	if t.Tests != nil && (t.LogicalOperator == LogicalOperatorOr || t.LogicalOperator == LogicalOperatorAnd) {
  1003  		w.WriteString(" ")
  1004  		t.Comments[4].printSource(w, true)
  1005  		t.LogicalOperator.printSource(w, v)
  1006  		w.WriteString(" ")
  1007  		t.Tests.printSource(w, v)
  1008  	} else if len(t.Comments[4]) > 0 {
  1009  		w.WriteString(" ")
  1010  		t.Comments[4].printSource(w, false)
  1011  	}
  1012  }
  1013  
  1014  func (t Tests) lastIsComment() bool {
  1015  	if t.Tests != nil {
  1016  		return t.Tests.lastIsComment()
  1017  	}
  1018  
  1019  	return len(t.Comments[4]) > 0
  1020  }
  1021  
  1022  func (t TestConsequence) printSource(w writer, v bool) {
  1023  	t.Test.printSource(w, v)
  1024  
  1025  	if t.Test.endsWithGrouping() {
  1026  		w.WriteString(";")
  1027  	}
  1028  
  1029  	ip := w.Indent()
  1030  
  1031  	if len(t.Comments) > 0 {
  1032  		w.WriteString(" ")
  1033  		t.Comments.printSource(w, true)
  1034  		ip.WriteString("then\n")
  1035  	} else {
  1036  		ip.WriteString(" then\n")
  1037  	}
  1038  
  1039  	t.Consequence.printSource(ip, v)
  1040  }
  1041  
  1042  func (ve Value) printSource(w writer, v bool) {
  1043  	if ve.Word != nil {
  1044  		ve.Word.printSource(w, v)
  1045  	} else if ve.Array != nil {
  1046  		iw := w
  1047  		ml := ve.isMultiline(v)
  1048  		lastHadComment := ml
  1049  
  1050  		if ml {
  1051  			iw = w.Indent()
  1052  		}
  1053  
  1054  		if len(ve.Comments[0]) > 0 {
  1055  			w.WriteString("(")
  1056  
  1057  			if len(ve.Comments[0]) > 0 {
  1058  				w.WriteString(" ")
  1059  				ve.Comments[0].printSource(w, len(ve.Array) > 0)
  1060  
  1061  				lastHadComment = true
  1062  			}
  1063  		} else {
  1064  			w.WriteString("(")
  1065  		}
  1066  
  1067  		if len(ve.Array) > 0 {
  1068  			lastHadComment = lastHadComment || len(ve.Array[0].Comments[0]) > 0
  1069  
  1070  			for _, word := range ve.Array {
  1071  				if lastHadComment {
  1072  					iw.WriteString("\n")
  1073  				} else if v && len(word.Comments[0]) == 0 {
  1074  					iw.WriteString(" ")
  1075  				}
  1076  
  1077  				word.printSource(iw, v)
  1078  
  1079  				lastHadComment = len(word.Comments[1]) != 0
  1080  			}
  1081  
  1082  			if v && !ml {
  1083  				iw.WriteString(" ")
  1084  			}
  1085  		}
  1086  
  1087  		if len(ve.Comments[1]) != 0 {
  1088  			w.WriteString("\n")
  1089  
  1090  			if lastHadComment {
  1091  				w.WriteString("\n")
  1092  			}
  1093  
  1094  			ve.Comments[1].printSource(w, false)
  1095  
  1096  			lastHadComment = true
  1097  		}
  1098  
  1099  		if lastHadComment || ml {
  1100  			w.WriteString("\n")
  1101  		}
  1102  
  1103  		w.WriteString(")")
  1104  	}
  1105  }
  1106  
  1107  func (wo WordOrOperator) printSource(w writer, v bool) {
  1108  	if wo.Operator != nil {
  1109  		w.WriteString(wo.Operator.Data)
  1110  	} else if wo.Word != nil {
  1111  		wo.Word.printSource(w, v)
  1112  	}
  1113  }
  1114  
  1115  func (wt WordOrToken) printSource(w writer, v bool) {
  1116  	if wt.Word != nil {
  1117  		wt.Word.printSource(w, v)
  1118  	} else if wt.Token != nil {
  1119  		w.WriteString(wt.Token.Data)
  1120  	}
  1121  }
  1122  
  1123  func (wp WordPart) printSource(w writer, v bool) {
  1124  	if wp.Part != nil {
  1125  		if len(wp.Part.Data) > 0 {
  1126  			w.WriteString(wp.Part.Data[:1])
  1127  			w.Underlying().WriteString(wp.Part.Data[1:])
  1128  		}
  1129  	} else if wp.ArithmeticExpansion != nil {
  1130  		wp.ArithmeticExpansion.printSource(w, v)
  1131  	} else if wp.CommandSubstitution != nil {
  1132  		wp.CommandSubstitution.printSource(w, v)
  1133  	} else if wp.ParameterExpansion != nil {
  1134  		wp.ParameterExpansion.printSource(w, v)
  1135  	} else if wp.BraceExpansion != nil {
  1136  		wp.BraceExpansion.printSource(w, v)
  1137  	}
  1138  }
  1139  
  1140  func (wd Word) printSource(w writer, v bool) {
  1141  	for _, word := range wd.Parts {
  1142  		word.printSource(w, v)
  1143  	}
  1144  }
  1145  
  1146  func (wd Word) firstPartIsProcessSubstitution() bool {
  1147  	return len(wd.Parts) > 0 && wd.Parts[0].CommandSubstitution != nil && (wd.Parts[0].CommandSubstitution.SubstitutionType == SubstitutionProcessInput || wd.Parts[0].CommandSubstitution.SubstitutionType == SubstitutionProcessOutput)
  1148  }
  1149