update deps

This commit is contained in:
Andrey Nering
2017-07-30 19:11:34 -03:00
parent 31faf05c3a
commit f91bbe9397
58 changed files with 5000 additions and 3954 deletions

View File

@@ -75,16 +75,15 @@ func (r *Runner) builtinCode(pos syntax.Pos, name string, args []string) int {
r.delVar(arg)
}
case "echo":
newline := true
newline, expand := true, false
echoOpts:
for len(args) > 0 {
switch args[0] {
case "-n":
newline = false
case "-e", "-E":
// TODO: what should be our default?
// exactly what is the difference in
// what we write?
case "-e":
expand = true
case "-E": // default
default:
break echoOpts
}
@@ -94,6 +93,9 @@ func (r *Runner) builtinCode(pos syntax.Pos, name string, args []string) int {
if i > 0 {
r.outf(" ")
}
if expand {
arg = r.expand(arg, true)
}
r.outf("%s", arg)
}
if newline {
@@ -104,11 +106,7 @@ func (r *Runner) builtinCode(pos syntax.Pos, name string, args []string) int {
r.errf("usage: printf format [arguments]\n")
return 2
}
var a []interface{}
for _, arg := range args[1:] {
a = append(a, arg)
}
r.outf(args[0], a...)
r.outf("%s", r.expand(args[0], false, args[1:]...))
case "break":
if !r.inLoop {
r.errf("break is only useful in a loop")
@@ -159,7 +157,8 @@ func (r *Runner) builtinCode(pos syntax.Pos, name string, args []string) int {
return 2
}
dir = r.relPath(dir)
if _, err := os.Stat(dir); err != nil {
info, err := os.Stat(dir)
if err != nil || !info.IsDir() {
return 1
}
r.Dir = dir
@@ -207,7 +206,7 @@ func (r *Runner) builtinCode(pos syntax.Pos, name string, args []string) int {
return 1
}
r2 := *r
r2.File = file
r2.Node = file
r2.Run()
return r2.exit
case "source", ".":
@@ -228,7 +227,7 @@ func (r *Runner) builtinCode(pos syntax.Pos, name string, args []string) int {
}
r2 := *r
r2.Params = args[1:]
r2.File = file
r2.Node = file
r2.Run()
return r2.exit
case "[":

View File

@@ -2,7 +2,8 @@
// See LICENSE for licensing information
// Package interp implements an interpreter that executes shell
// programs. It aims to support POSIX.
// programs. It aims to support POSIX, but its support is not complete
// yet. It also supports some Bash features.
//
// This package is a work in progress and EXPERIMENTAL; its API is not
// subject to the 1.x backwards compatibility guarantee.

View File

@@ -29,8 +29,7 @@ import (
// concurrent use, consider a workaround like hiding writes behind a
// mutex.
type Runner struct {
// TODO: syntax.Node instead of *syntax.File?
File *syntax.File
Node syntax.Node
// Env specifies the environment of the interpreter.
// If Env is nil, Run uses the current process's environment.
@@ -243,7 +242,16 @@ func (r *Runner) Run() error {
}
r.Dir = dir
}
r.stmts(r.File.StmtList)
switch x := r.Node.(type) {
case *syntax.File:
r.stmts(x.StmtList)
case *syntax.Stmt:
r.stmt(x)
case syntax.Command:
r.cmd(x)
default:
return fmt.Errorf("Node can only be File, Stmt, or Command: %T", x)
}
r.lastExit()
if r.err == ExitCode(0) {
r.err = nil
@@ -259,6 +267,56 @@ func (r *Runner) errf(format string, a ...interface{}) {
fmt.Fprintf(r.Stderr, format, a...)
}
func (r *Runner) expand(format string, onlyChars bool, args ...string) string {
var buf bytes.Buffer
esc, fmt := false, false
for _, c := range format {
if esc {
esc = false
switch c {
case 'n':
buf.WriteRune('\n')
case 'r':
buf.WriteRune('\r')
case 't':
buf.WriteRune('\t')
case '\\':
buf.WriteRune('\\')
default:
buf.WriteRune('\\')
buf.WriteRune(c)
}
continue
}
if fmt {
fmt = false
arg := ""
if len(args) > 0 {
arg, args = args[0], args[1:]
}
switch c {
case 's':
buf.WriteString(arg)
case 'd':
// round-trip to convert invalid to 0
n, _ := strconv.Atoi(arg)
buf.WriteString(strconv.Itoa(n))
default:
r.runErr(syntax.Pos{}, "unhandled format char: %c", c)
}
continue
}
if c == '\\' {
esc = true
} else if !onlyChars && c == '%' {
fmt = true
} else {
buf.WriteRune(c)
}
}
return buf.String()
}
func fieldJoin(parts []fieldPart) string {
var buf bytes.Buffer
for _, part := range parts {
@@ -394,18 +452,6 @@ func (r *Runner) assignValue(as *syntax.Assign) varValue {
}
func (r *Runner) stmtSync(st *syntax.Stmt) {
oldVars := r.cmdVars
for _, as := range st.Assigns {
val := r.assignValue(as)
if st.Cmd == nil {
r.setVar(as.Name.Value, val)
continue
}
if r.cmdVars == nil {
r.cmdVars = make(map[string]varValue, len(st.Assigns))
}
r.cmdVars[as.Name.Value] = val
}
oldIn, oldOut, oldErr := r.Stdin, r.Stdout, r.Stderr
for _, rd := range st.Redirs {
cls, err := r.redir(rd)
@@ -425,7 +471,6 @@ func (r *Runner) stmtSync(st *syntax.Stmt) {
if st.Negated {
r.exit = oneIf(r.exit == 0)
}
r.cmdVars = oldVars
r.Stdin, r.Stdout, r.Stderr = oldIn, oldOut, oldErr
}
@@ -448,8 +493,22 @@ func (r *Runner) cmd(cm syntax.Command) {
r2.stmts(x.StmtList)
r.exit = r2.exit
case *syntax.CallExpr:
if len(x.Args) == 0 {
for _, as := range x.Assigns {
r.setVar(as.Name.Value, r.assignValue(as))
}
break
}
oldVars := r.cmdVars
if r.cmdVars == nil {
r.cmdVars = make(map[string]varValue, len(x.Assigns))
}
for _, as := range x.Assigns {
r.cmdVars[as.Name.Value] = r.assignValue(as)
}
fields := r.fields(x.Args)
r.call(x.Args[0].Pos(), fields[0], fields[1:])
r.cmdVars = oldVars
case *syntax.BinaryCmd:
switch x.Op {
case syntax.AndStmt:
@@ -612,10 +671,17 @@ func (r *Runner) redir(rd *syntax.Redirect) (io.Closer, error) {
case syntax.RdrOut, syntax.RdrAll:
mode = os.O_RDWR | os.O_CREATE | os.O_TRUNC
}
f, err := os.OpenFile(r.relPath(arg), mode, 0644)
if err != nil {
// TODO: print to stderr?
return nil, err
var f io.ReadWriteCloser
switch arg {
case "/dev/null":
f = devNull{}
default:
var err error
f, err = os.OpenFile(r.relPath(arg), mode, 0644)
if err != nil {
// TODO: print to stderr?
return nil, err
}
}
switch rd.Op {
case syntax.RdrIn:
@@ -685,10 +751,11 @@ func (r *Runner) wordFields(wps []syntax.WordPart, quoted bool) [][]fieldPart {
curField = append(curField, fieldPart{val: s})
case *syntax.SglQuoted:
allowEmpty = true
curField = append(curField, fieldPart{
quoted: true,
val: x.Value,
})
fp := fieldPart{quoted: true, val: x.Value}
if x.Dollar {
fp.val = r.expand(fp.val, true)
}
curField = append(curField, fp)
case *syntax.DblQuoted:
allowEmpty = true
if len(x.Parts) == 1 {

View File

@@ -56,22 +56,23 @@ func wordBreak(r rune) bool {
}
func (p *Parser) rune() rune {
if p.r == '\n' {
// p.r instead of b so that newline
// character positions don't have col 0.
p.npos.line++
p.npos.col = 1
} else {
p.npos.col += p.w
}
retry:
if p.bsp < len(p.bs) {
if b := p.bs[p.bsp]; b < utf8.RuneSelf {
p.bsp++
if p.r == '\n' {
// p.r instead of b so that newline
// character positions don't have col 0.
p.npos.line++
p.npos.col = 0
}
p.npos.col++
if p.litBs != nil {
p.litBs = append(p.litBs, b)
}
r := rune(b)
p.r = r
p.r, p.w = r, 1
return r
}
if p.bsp+utf8.UTFMax >= len(p.bs) {
@@ -85,10 +86,10 @@ retry:
p.litBs = append(p.litBs, p.bs[p.bsp:p.bsp+w]...)
}
p.bsp += w
p.npos.col += uint16(w)
if p.r == utf8.RuneError && w == 1 {
p.posErr(p.npos, "invalid UTF-8 encoding")
}
p.w = uint16(w)
} else {
if p.r == utf8.RuneSelf {
} else if p.fill(); p.bs == nil {
@@ -134,9 +135,7 @@ func (p *Parser) fill() {
func (p *Parser) nextKeepSpaces() {
r := p.r
if p.pos = p.getPos(); r > utf8.RuneSelf {
p.pos = posAddCol(p.pos, -1) // TODO
}
p.pos = p.getPos()
switch p.quote {
case paramExpRepl:
switch r {
@@ -225,9 +224,7 @@ skipSpace:
break skipSpace
}
}
if p.pos = p.getPos(); r > utf8.RuneSelf {
p.pos = posAddCol(p.pos, -1) // TODO
}
p.pos = p.getPos()
switch {
case p.quote&allRegTokens != 0:
switch r {
@@ -810,7 +807,7 @@ loop:
}
func (p *Parser) advanceLitNone(r rune) {
p.asPos = 0
p.eqlOffs = 0
tok := _LitWord
loop:
for p.newLit(r); r != utf8.RuneSelf; r = p.rune() {
@@ -854,7 +851,7 @@ loop:
break loop
}
case '=':
p.asPos = len(p.litBs) - 1
p.eqlOffs = len(p.litBs) - 1
case '[':
if p.lang != LangPOSIX && len(p.litBs) > 1 && p.litBs[0] != '[' {
tok = _Lit

View File

@@ -40,6 +40,16 @@ func (s StmtList) pos() Pos {
return Pos{}
}
func (s StmtList) end() Pos {
if len(s.Last) > 0 {
return s.Last[len(s.Last)-1].End()
}
if len(s.Stmts) > 0 {
return s.Stmts[len(s.Stmts)-1].End()
}
return Pos{}
}
func (s StmtList) empty() bool {
return len(s.Stmts) == 0 && len(s.Last) == 0
}
@@ -52,20 +62,14 @@ type Pos struct {
// Offset returns the byte offset of the position in the original
// source file. Byte offsets start at 0.
func (p Pos) Offset() uint {
return uint(p.offs)
}
func (p Pos) Offset() uint { return uint(p.offs) }
// Line returns the line number of the position, starting at 1.
func (p Pos) Line() uint {
return uint(p.line)
}
func (p Pos) Line() uint { return uint(p.line) }
// Col returns the column number of the position, starting at 0. It
// Col returns the column number of the position, starting at 1. It
// counts in bytes.
func (p Pos) Col() uint {
return uint(p.col)
}
func (p Pos) Col() uint { return uint(p.col) }
func (p Pos) String() string {
return fmt.Sprintf("%d:%d", p.Line(), p.Col())
@@ -113,9 +117,8 @@ type Comment struct {
func (c *Comment) Pos() Pos { return c.Hash }
func (c *Comment) End() Pos { return posAddCol(c.Hash, len(c.Text)) }
// Stmt represents a statement, otherwise known as a compound command.
// It is compromised of a command and other components that may come
// before or after it.
// Stmt represents a statement. It is compromised of a command and other
// components that may come before or after it.
type Stmt struct {
Comments []Comment
Cmd Command
@@ -125,8 +128,7 @@ type Stmt struct {
Background bool // stmt &
Coprocess bool // mksh's |&
Assigns []*Assign // a=x b=y stmt
Redirs []*Redirect // stmt >a <b
Redirs []*Redirect // stmt >a <b
}
func (s *Stmt) Pos() Pos { return s.Position }
@@ -141,17 +143,14 @@ func (s *Stmt) End() Pos {
if s.Cmd != nil {
end = s.Cmd.End()
}
if len(s.Assigns) > 0 {
end = posMax(end, s.Assigns[len(s.Assigns)-1].End())
}
if len(s.Redirs) > 0 {
end = posMax(end, s.Redirs[len(s.Redirs)-1].End())
}
return end
}
// Command represents all nodes that are simple commands, which are
// directly placed in a Stmt.
// Command represents all nodes that are simple or compound commands,
// including function declarations.
//
// These are *CallExpr, *IfClause, *WhileClause, *ForClause,
// *CaseClause, *Block, *Subshell, *BinaryCmd, *FuncDecl, *ArithmCmd,
@@ -237,13 +236,29 @@ func (r *Redirect) Pos() Pos {
}
func (r *Redirect) End() Pos { return r.Word.End() }
// CallExpr represents a command execution or function call.
// CallExpr represents a command execution or function call, otherwise
// known as a simple command.
//
// If Args is empty, Assigns apply to the shell environment. Otherwise,
// they only apply to the call.
type CallExpr struct {
Args []*Word
Assigns []*Assign // a=x b=y args
Args []*Word
}
func (c *CallExpr) Pos() Pos { return c.Args[0].Pos() }
func (c *CallExpr) End() Pos { return c.Args[len(c.Args)-1].End() }
func (c *CallExpr) Pos() Pos {
if len(c.Assigns) > 0 {
return c.Assigns[0].Pos()
}
return c.Args[0].Pos()
}
func (c *CallExpr) End() Pos {
if len(c.Args) == 0 {
return c.Assigns[len(c.Assigns)-1].End()
}
return c.Args[len(c.Args)-1].End()
}
// Subshell represents a series of commands that should be executed in a
// nested shell environment.

View File

@@ -11,6 +11,8 @@ import (
"unicode/utf8"
)
// KeepComments makes the parser parse comments and attach them to
// nodes, as opposed to discarding them.
func KeepComments(p *Parser) { p.keepComments = true }
type LangVariant int
@@ -21,10 +23,13 @@ const (
LangMirBSDKorn
)
// Variant changes the shell language variant that the parser will
// accept.
func Variant(l LangVariant) func(*Parser) {
return func(p *Parser) { p.lang = l }
}
// NewParser allocates a new Parser and applies any number of options.
func NewParser(options ...func(*Parser)) *Parser {
p := &Parser{helperBuf: new(bytes.Buffer)}
for _, opt := range options {
@@ -35,11 +40,14 @@ func NewParser(options ...func(*Parser)) *Parser {
// Parse reads and parses a shell program with an optional name. It
// returns the parsed program if no issues were encountered. Otherwise,
// an error is returned.
func (p *Parser) Parse(src io.Reader, name string) (*File, error) {
// an error is returned. Reads from r are buffered.
//
// Parse can be called more than once, but not concurrently. That is, a
// Parser can be reused once it is done working.
func (p *Parser) Parse(r io.Reader, name string) (*File, error) {
p.reset()
p.f = &File{Name: name}
p.src = src
p.src = r
p.rune()
p.next()
p.f.StmtList = p.stmts()
@@ -51,11 +59,14 @@ func (p *Parser) Parse(src io.Reader, name string) (*File, error) {
return p.f, p.err
}
// Parser holds the internal state of the parsing mechanism of a
// program.
type Parser struct {
src io.Reader
bs []byte // current chunk of read bytes
bsp int // pos within chunk for the next rune
r rune
bsp int // pos within chunk for the rune after r
r rune // next rune
w uint16 // width of r
f *File
@@ -70,10 +81,10 @@ type Parser struct {
offs int
pos Pos // position of tok
npos Pos // next position
npos Pos // next position (of r)
quote quoteState // current lexer state
asPos int // position of '=' in a literal
quote quoteState // current lexer state
eqlOffs int // position of '=' in val (a literal)
keepComments bool
lang LangVariant
@@ -105,17 +116,20 @@ type Parser struct {
const bufSize = 1 << 10
func (p *Parser) reset() {
p.tok, p.val = illegalTok, ""
p.eqlOffs = 0
p.bs, p.bsp = nil, 0
p.offs = 0
p.npos = Pos{line: 1}
p.r, p.err, p.readErr = 0, nil, nil
p.npos = Pos{line: 1, col: 1}
p.r, p.w = 0, 0
p.err, p.readErr = nil, nil
p.quote, p.forbidNested = noState, false
p.heredocs, p.buriedHdocs = p.heredocs[:0], 0
p.accComs, p.curComs = nil, &p.accComs
}
func (p *Parser) getPos() Pos {
p.npos.offs = uint32(p.offs + p.bsp - 1)
p.npos.offs = uint32(p.offs + p.bsp - int(p.w))
return p.npos
}
@@ -1145,7 +1159,7 @@ func ValidName(val string) bool {
}
func (p *Parser) hasValidIdent() bool {
if end := p.asPos; end > 0 {
if end := p.eqlOffs; end > 0 {
if p.val[end-1] == '+' && p.lang != LangPOSIX {
end--
}
@@ -1158,9 +1172,9 @@ func (p *Parser) hasValidIdent() bool {
func (p *Parser) getAssign(needEqual bool) *Assign {
as := &Assign{}
if p.asPos > 0 { // foo=bar
nameEnd := p.asPos
if p.lang != LangPOSIX && p.val[p.asPos-1] == '+' {
if p.eqlOffs > 0 { // foo=bar
nameEnd := p.eqlOffs
if p.lang != LangPOSIX && p.val[p.eqlOffs-1] == '+' {
// a+=b
as.Append = true
nameEnd--
@@ -1168,9 +1182,9 @@ func (p *Parser) getAssign(needEqual bool) *Assign {
as.Name = p.lit(p.pos, p.val[:nameEnd])
// since we're not using the entire p.val
as.Name.ValueEnd = posAddCol(as.Name.ValuePos, nameEnd)
left := p.lit(posAddCol(p.pos, 1), p.val[p.asPos+1:])
left := p.lit(posAddCol(p.pos, 1), p.val[p.eqlOffs+1:])
if left.Value != "" {
left.ValuePos = posAddCol(left.ValuePos, p.asPos)
left.ValuePos = posAddCol(left.ValuePos, p.eqlOffs)
as.Value = p.word(p.wps(left))
}
p.next()
@@ -1371,25 +1385,6 @@ func (p *Parser) getStmt(readEnd, binCmd bool) (s *Stmt, gotEnd bool) {
}
func (p *Parser) gotStmtPipe(s *Stmt) *Stmt {
preLoop:
for {
switch p.tok {
case _Lit, _LitWord:
if !p.hasValidIdent() {
break preLoop
}
s.Assigns = append(s.Assigns, p.getAssign(true))
case rdrOut, appOut, rdrIn, dplIn, dplOut, clbOut, rdrInOut,
hdoc, dashHdoc, wordHdoc, rdrAll, appAll, _LitRedir:
p.doRedirect(s)
default:
break preLoop
}
switch {
case p.newLine, p.tok == _EOF, p.tok == semicolon:
return s
}
}
switch p.tok {
case _LitWord:
switch p.val {
@@ -1458,6 +1453,10 @@ preLoop:
if s.Cmd != nil {
break
}
if p.hasValidIdent() {
s.Cmd = p.callExpr(s, nil, true)
break
}
name := p.lit(p.pos, p.val)
if p.next(); p.gotSameLine(leftParen) {
p.follow(name.ValuePos, "foo(", rightParen)
@@ -1466,8 +1465,16 @@ preLoop:
}
s.Cmd = p.funcDecl(name, name.ValuePos)
} else {
s.Cmd = p.callExpr(s, p.word(p.wps(name)))
s.Cmd = p.callExpr(s, p.word(p.wps(name)), false)
}
case rdrOut, appOut, rdrIn, dplIn, dplOut, clbOut, rdrInOut,
hdoc, dashHdoc, wordHdoc, rdrAll, appAll, _LitRedir:
p.doRedirect(s)
switch {
case p.newLine, p.tok == _EOF, p.tok == semicolon:
return s
}
s.Cmd = p.callExpr(s, nil, false)
case bckQuote:
if p.quote == subCmdBckquo {
return s
@@ -1476,17 +1483,21 @@ preLoop:
case _Lit, dollBrace, dollDblParen, dollParen, dollar, cmdIn, cmdOut,
sglQuote, dollSglQuote, dblQuote, dollDblQuote, dollBrack,
globQuest, globStar, globPlus, globAt, globExcl:
if p.tok == _Lit && p.hasValidIdent() {
s.Cmd = p.callExpr(s, nil, true)
break
}
w := p.word(p.wordParts())
if p.gotSameLine(leftParen) && p.err == nil {
p.posErr(w.Pos(), "invalid func name")
}
s.Cmd = p.callExpr(s, w)
s.Cmd = p.callExpr(s, w, false)
case leftParen:
s.Cmd = p.subshell()
case dblLeftParen:
s.Cmd = p.arithmExpCmd()
default:
if len(s.Redirs) == 0 && len(s.Assigns) == 0 {
if len(s.Redirs) == 0 {
return nil
}
}
@@ -1829,7 +1840,9 @@ func (p *Parser) testExprBase(ftok token, fpos Pos) TestExpr {
case exclMark:
u := &UnaryTest{OpPos: p.pos, Op: TsNot}
p.next()
u.X = p.testExpr(token(u.Op), u.OpPos, false)
if u.X = p.testExpr(token(u.Op), u.OpPos, false); u.X == nil {
p.followErrExp(u.OpPos, u.Op.String())
}
return u
case tsExists, tsRegFile, tsDirect, tsCharSp, tsBlckSp, tsNmPipe,
tsSocket, tsSmbLink, tsSticky, tsGIDSet, tsUIDSet, tsGrpOwn,
@@ -1837,7 +1850,7 @@ func (p *Parser) testExprBase(ftok token, fpos Pos) TestExpr {
tsFdTerm, tsEmpStr, tsNempStr, tsOptSet, tsVarSet, tsRefVar:
u := &UnaryTest{OpPos: p.pos, Op: UnTestOperator(p.tok)}
p.next()
u.X = p.followWordTok(ftok, fpos)
u.X = p.followWordTok(token(u.Op), u.OpPos)
return u
case leftParen:
pe := &ParenTest{Lparen: p.pos}
@@ -1866,6 +1879,8 @@ func (p *Parser) declClause() *DeclClause {
for !p.newLine && !stopToken(p.tok) && !p.peekRedir() {
if (p.tok == _Lit || p.tok == _LitWord) && p.hasValidIdent() {
ds.Assigns = append(ds.Assigns, p.getAssign(false))
} else if p.eqlOffs > 0 {
p.curErr("invalid var name")
} else if p.tok == _LitWord {
ds.Assigns = append(ds.Assigns, &Assign{
Naked: true,
@@ -1975,24 +1990,41 @@ func (p *Parser) bashFuncDecl() *FuncDecl {
return p.funcDecl(name, fpos)
}
func (p *Parser) callExpr(s *Stmt, w *Word) *CallExpr {
func (p *Parser) callExpr(s *Stmt, w *Word, assign bool) Command {
ce := p.call(w)
if w == nil {
ce.Args = ce.Args[:0]
}
if assign {
ce.Assigns = append(ce.Assigns, p.getAssign(true))
}
loop:
for !p.newLine {
switch p.tok {
case _EOF, semicolon, and, or, andAnd, orOr, orAnd,
dblSemicolon, semiAnd, dblSemiAnd, semiOr:
return ce
break loop
case _LitWord:
if len(ce.Args) == 0 && p.hasValidIdent() {
ce.Assigns = append(ce.Assigns, p.getAssign(true))
break
}
ce.Args = append(ce.Args, p.word(
p.wps(p.lit(p.pos, p.val)),
))
p.next()
case _Lit:
if len(ce.Args) == 0 && p.hasValidIdent() {
ce.Assigns = append(ce.Assigns, p.getAssign(true))
break
}
ce.Args = append(ce.Args, p.word(p.wordParts()))
case bckQuote:
if p.quote == subCmdBckquo {
return ce
break loop
}
fallthrough
case _Lit, dollBrace, dollDblParen, dollParen, dollar, cmdIn, cmdOut,
case dollBrace, dollDblParen, dollParen, dollar, cmdIn, cmdOut,
sglQuote, dollSglQuote, dblQuote, dollDblQuote, dollBrack,
globQuest, globStar, globPlus, globAt, globExcl:
ce.Args = append(ce.Args, p.word(p.wordParts()))
@@ -2003,13 +2035,19 @@ func (p *Parser) callExpr(s *Stmt, w *Word) *CallExpr {
p.curErr("%s can only be used to open an arithmetic cmd", p.tok)
case rightParen:
if p.quote == subCmd {
return ce
break loop
}
fallthrough
default:
p.curErr("a command can only contain words and redirects")
}
}
if len(ce.Assigns) == 0 && len(ce.Args) == 0 {
return nil
}
if len(ce.Args) == 0 {
ce.Args = nil
}
return ce
}

View File

@@ -7,14 +7,21 @@ import (
"bufio"
"io"
"strings"
"unicode"
)
func Indent(spaces int) func(*Printer) {
// Indent sets the number of spaces used for indentation. If set to 0,
// tabs will be used instead.
func Indent(spaces uint) func(*Printer) {
return func(p *Printer) { p.indentSpaces = spaces }
}
// BinaryNextLine will make binary operators appear on the next line
// when a binary command, such as a pipe, spans multiple lines. A
// backslash will be used.
func BinaryNextLine(p *Printer) { p.binNextLine = true }
// NewPrinter allocates a new Printer and applies any number of options.
func NewPrinter(options ...func(*Printer)) *Printer {
p := &Printer{
bufWriter: bufio.NewWriter(nil),
@@ -26,7 +33,8 @@ func NewPrinter(options ...func(*Printer)) *Printer {
return p
}
// Print "pretty-prints" the given AST file to the given writer.
// Print "pretty-prints" the given AST file to the given writer. Writes
// to w are buffered.
func (p *Printer) Print(w io.Writer, f *File) error {
p.reset()
p.bufWriter.Reset(w)
@@ -42,25 +50,27 @@ type bufWriter interface {
Flush() error
}
// Printer holds the internal state of the printing mechanism of a
// program.
type Printer struct {
bufWriter
indentSpaces int
indentSpaces uint
binNextLine bool
wantSpace bool
wantNewline bool
wroteSemi bool
commentPadding int
commentPadding uint
// line is the current line number
line uint
// lastLevel is the last level of indentation that was used.
lastLevel int
lastLevel uint
// level is the current level of indentation.
level int
level uint
// levelIncs records which indentation level increments actually
// took place, to revert them once their section ends.
levelIncs []bool
@@ -85,8 +95,8 @@ func (p *Printer) reset() {
p.pendingHdocs = p.pendingHdocs[:0]
}
func (p *Printer) spaces(n int) {
for i := 0; i < n; i++ {
func (p *Printer) spaces(n uint) {
for i := uint(0); i < n; i++ {
p.WriteByte(' ')
}
}
@@ -148,10 +158,10 @@ func (p *Printer) indent() {
switch {
case p.level == 0:
case p.indentSpaces == 0:
for i := 0; i < p.level; i++ {
for i := uint(0); i < p.level; i++ {
p.WriteByte('\t')
}
case p.indentSpaces > 0:
default:
p.spaces(p.indentSpaces * p.level)
}
}
@@ -219,7 +229,7 @@ func (p *Printer) comment(c Comment) {
}
p.line = c.Hash.Line()
p.WriteByte('#')
p.WriteString(c.Text)
p.WriteString(strings.TrimRightFunc(c.Text, unicode.IsSpace))
}
func (p *Printer) comments(cs []Comment) {
@@ -529,7 +539,6 @@ func (p *Printer) stmt(s *Stmt) {
if s.Negated {
p.spacedString("!")
}
p.assigns(s.Assigns, true)
var startRedirs int
if s.Cmd != nil {
startRedirs = p.command(s.Cmd, s.Redirs)
@@ -573,6 +582,7 @@ func (p *Printer) command(cmd Command, redirs []*Redirect) (startRedirs int) {
}
switch x := cmd.(type) {
case *CallExpr:
p.assigns(x.Assigns, true)
if len(x.Args) <= 1 {
p.wordJoin(x.Args)
return 0
@@ -704,6 +714,10 @@ func (p *Printer) command(cmd Command, redirs []*Redirect) (startRedirs int) {
p.WriteByte(')')
p.wantSpace = true
sep := len(ci.Stmts) > 1 || ci.StmtList.pos().Line() > p.line
if ci.OpPos != x.Esac && !ci.StmtList.empty() &&
ci.OpPos.Line() > ci.StmtList.end().Line() {
sep = true
}
sl := ci.StmtList
p.nestedStmts(sl, Pos{})
p.level++
@@ -868,7 +882,7 @@ func (p *Printer) stmts(sl StmtList) {
}
if inlineIndent > 0 {
if l := p.stmtCols(s); l > 0 {
p.commentPadding = inlineIndent - l
p.commentPadding = uint(inlineIndent - l)
}
lastIndentedLine = p.line
}

View File

@@ -167,7 +167,7 @@ func (s *simplifier) inlineSubshell(stmts []*Stmt) []*Stmt {
for len(stmts) == 1 {
st := stmts[0]
if st.Negated || st.Background || st.Coprocess ||
len(st.Assigns) > 0 || len(st.Redirs) > 0 {
len(st.Redirs) > 0 {
break
}
sub, _ := st.Cmd.(*Subshell)

View File

@@ -33,9 +33,6 @@ func Walk(node Node, f func(Node) bool) {
if x.Cmd != nil {
Walk(x.Cmd, f)
}
for _, a := range x.Assigns {
Walk(a, f)
}
for _, r := range x.Redirs {
Walk(r, f)
}
@@ -61,6 +58,9 @@ func Walk(node Node, f func(Node) bool) {
Walk(x.Hdoc, f)
}
case *CallExpr:
for _, a := range x.Assigns {
Walk(a, f)
}
walkWords(x.Args, f)
case *Subshell:
walkStmts(x.StmtList, f)