func (n *PipeNode) string() (string, bool) {
	if n.multi {
		return n.multiString(), true
	}
	ret := ""
	for i := 0; i < len(n.cmds); i++ {
		ret += n.cmds[i].String()
		if i < (len(n.cmds) - 1) {
			ret += " | "
		}
	}
	return ret, false
}
String returns the string representation of pipeline statement
package ast
import (
	"fmt"
	"strconv"
	"strings"
)
func (s *StringExpr) String() string {
	if s.quoted {
		return `"` + stringify(s.str) + `"`
	}
	return s.str
}
func (i *IntExpr) String() string {
	return strconv.Itoa(i.val)
}
func (l *ListExpr) string() (string, bool) {
	elems := make([]string, len(l.List))
	columnCount := 0
	forceMulti := false
	for i := 0; i < len(l.List); i++ {
		if l.List[i].Type() == NodeListExpr {
			forceMulti = true
		}
		elems[i] = l.List[i].String()
		columnCount += len(elems[i])
	}
	if columnCount+len(elems) > 50 || forceMulti {
		forceMulti = true
		return "(\n\t" + strings.Join(elems, "\n\t") + "\n)", forceMulti
	}
	return "(" + strings.Join(elems, " ") + ")", false
}
func (l *ListExpr) String() string {
	str, _ := l.string()
	return str
}
func (c *ConcatExpr) String() string {
	ret := ""
	for i := 0; i < len(c.concat); i++ {
		ret += c.concat[i].String()
		if i < (len(c.concat) - 1) {
			ret += "+"
		}
	}
	return ret
}
func (v *VarExpr) String() string {
	if v.IsVariadic {
		return v.Name + "..."
	}
	return v.Name
}
func (i *IndexExpr) String() string {
	ret := fmt.Sprintf("%s[%s]", i.Var, i.Index)
	if i.IsVariadic {
		return ret + "..."
	}
	return ret
}
func (l *BlockNode) adjustGroupAssign(node assignable, nodes []Node) {
	var (
		eqSpace int = node.getEqSpace()
		i       int
	)
	lhs := getlhs(node)
	eqSpace = len(lhs) + 1
	for i = 0; i < len(nodes); i++ {
		assign, ok := nodes[i].(assignable)
		if !ok {
			break
		}
		if len(getlhs(assign))+1 > eqSpace {
			eqSpace = len(getlhs(assign)) + 1
		}
	}
	for j := 0; j < i; j++ {
		knode := nodes[j].(assignable)
		knode.setEqSpace(eqSpace)
	}
	node.setEqSpace(eqSpace)
}
func (l *BlockNode) String() string {
	nodes := l.Nodes
	content := make([]string, 0, 8192)
	last := (len(nodes) - 1)
	for i := 0; i < len(nodes); i++ {
		addEOL := false
		node := nodes[i]
		nodebytes := node.String()
		if i == 0 && node.Type() == NodeComment &&
			strings.HasPrefix(node.String(), "#!") {
			addEOL = true
		} else if (node.Type() == NodeComment) && i < last {
			nextNode := nodes[i+1]
			if nextNode.Line() > node.Line()+1 {
				addEOL = true
			}
		} else if i < last {
			nextNode := nodes[i+1]
			if node.Type() != nextNode.Type() {
				addEOL = true
			} else if node.Type() == NodeFnDecl {
				addEOL = true
			} else if node.Type() == NodeAssign || node.Type() == NodeExecAssign {
				nodeAssign := node.(assignable)
				if nodeAssign.getEqSpace() == -1 {
					l.adjustGroupAssign(nodeAssign, nodes[i+1:])
				}
				nodebytes, addEOL = nodeAssign.string()
			}
		}
		if addEOL {
			nodebytes += "\n"
		}
		content = append(content, nodebytes)
	}
	return strings.Join(content, "\n")
}
func (n *ImportNode) String() string {
	return `import ` + n.Path.String()
}
func (n *SetenvNode) String() string {
	if n.assign == nil {
		return "setenv " + n.Name
	}
	return "setenv " + n.assign.String()
}
func (n *NameNode) String() string {
	if n.Index != nil {
		return n.Ident + "[" + n.Index.String() + "]"
	}
	return n.Ident
}
func (n *AssignNode) string() (string, bool) {
	var (
		multi bool
	)
	objs := n.Values
	lhs := getlhs(n)
	ret := ""
	for i := 0; i < len(objs); i++ {
		var (
			objStr   string
			objmulti bool
		)
		obj := objs[i]
		if obj.Type().IsExpr() {
			if obj.Type() == NodeListExpr {
				lobj := obj.(*ListExpr)
				objStr, objmulti = lobj.string()
			} else {
				objStr = obj.String()
			}
		}
		if i == 0 {
			if n.eqSpace > len(lhs) && !multi {
				ret = lhs + strings.Repeat(" ", n.eqSpace-len(lhs)) + "= " + objStr
			} else {
				ret = lhs + " = " + objStr
			}
		} else if i < len(objs)-1 {
			ret = ret + ", " + objStr + ", "
		} else {
			ret = ret + ", " + objStr
		}
		if objmulti && !multi {
			multi = true
		}
	}
	return ret, multi
}
func (n *AssignNode) String() string {
	str, _ := n.string()
	return str
}
func (n *ExecAssignNode) string() (string, bool) {
	var (
		cmdStr string
		multi  bool
	)
	lhs := getlhs(n)
	if n.cmd.Type() == NodeCommand {
		cmd := n.cmd.(*CommandNode)
		cmdStr, multi = cmd.string()
	} else if n.cmd.Type() == NodePipe {
		cmd := n.cmd.(*PipeNode)
		cmdStr, multi = cmd.string()
	} else {
		cmd := n.cmd.(*FnInvNode)
		cmdStr, multi = cmd.string()
	}
	if n.eqSpace > len(lhs) {
		ret := lhs + strings.Repeat(" ", n.eqSpace-len(lhs)) + "<= " + cmdStr
		return ret, multi
	}
	return lhs + " <= " + cmdStr, multi
}
func (n *ExecAssignNode) String() string {
	str, _ := n.string()
	return str
}
func (n *CommandNode) toStringParts() ([]string, int) {
	var (
		content  []string
		line     string
		last     = len(n.args) - 1
		totalLen = 0
	)
	for i := 0; i < len(n.args); i += 2 {
		var next string
		arg := n.args[i].String()
		if i < last {
			next = n.args[i+1].String()
		}
		if i == 0 {
			arg = n.name + " " + arg
		}
		if arg[0] == '-' {
			if line != "" {
				content = append(content, line)
				line = ""
			}
			if len(next) > 0 && next[0] != '-' {
				if line == "" {
					line += arg + " " + next
				} else {
					line += " " + arg + " " + next
				}
			} else {
				content = append(content, arg, next)
			}
		} else if next != "" {
			if line == "" {
				line += arg + " " + next
			} else {
				line += " " + arg + " " + next
			}
		} else {
			if line == "" {
				line += arg
			} else {
				line += " " + arg
			}
		}
		totalLen += len(arg) + len(next) + 1
	}
	if line != "" {
		content = append(content, line)
	}
	if len(content) == 0 {
		content = append(content, n.name)
	}
	for i := 0; i < len(n.redirs); i++ {
		rstr := n.redirs[i].String()
		totalLen += len(rstr) + 1
		content = append(content, rstr)
	}
	return content, totalLen
}
func (n *CommandNode) multiString() string {
	content, totalLen := n.toStringParts()
	if totalLen < 50 {
		return "(" + strings.Join(content, " ") + ")"
	}
	content[0] = "\t" + content[0]
	gentab := func(n int) string { return strings.Repeat("\t", n) }
	tabLen := (len(content[0]) + 7) / 8
	for i := 1; i < len(content); i++ {
		content[i] = gentab(tabLen) + content[i]
	}
	return "(\n" + strings.Join(content, "\n") + "\n)"
}
func (n *CommandNode) string() (string, bool) {
	if n.multi {
		return n.multiString(), true
	}
	var content []string
	content = append(content, n.name)
	for i := 0; i < len(n.args); i++ {
		content = append(content, n.args[i].String())
	}
	for i := 0; i < len(n.redirs); i++ {
		content = append(content, n.redirs[i].String())
	}
	return strings.Join(content, " "), false
}
func (n *CommandNode) String() string {
	str, _ := n.string()
	return str
}
func (n *PipeNode) multiString() string {
	totalLen := 0
	type cmdData struct {
		content  []string
		totalLen int
	}
	content := make([]cmdData, len(n.cmds))
	for i := 0; i < len(n.cmds); i++ {
		cmdContent, cmdLen := n.cmds[i].toStringParts()
		content[i] = cmdData{
			cmdContent,
			cmdLen,
		}
		totalLen += cmdLen
	}
	if totalLen+3 < 50 {
		result := "("
		for i := 0; i < len(content); i++ {
			result += strings.Join(content[i].content, " ")
			if i < len(content)-1 {
				result += " | "
			}
		}
		return result + ")"
	}
	gentab := func(n int) string { return strings.Repeat("\t", n) }
	result := "(\n"
	for i := 0; i < len(content); i++ {
		cmdContent := content[i].content
		cmdContent[0] = "\t" + cmdContent[0]
		tabLen := (len(cmdContent[0]) + 7) / 8
		for j := 1; j < len(cmdContent); j++ {
			cmdContent[j] = gentab(tabLen) + cmdContent[j]
		}
		result += strings.Join(cmdContent, "\n")
		if i < len(content)-1 {
			result += " |\n"
		}
	}
	return result + "\n)"
}