diff --git a/vendor/github.com/mvdan/sh/interp/arith.go b/vendor/github.com/mvdan/sh/interp/arith.go index 217a6514..9999fb0d 100644 --- a/vendor/github.com/mvdan/sh/interp/arith.go +++ b/vendor/github.com/mvdan/sh/interp/arith.go @@ -11,8 +11,8 @@ import ( func (r *Runner) arithm(expr syntax.ArithmExpr) int { switch x := expr.(type) { - case *syntax.Lit: - str := x.Value + case *syntax.Word: + str := r.loneWord(x) // recursively fetch vars for { val := r.getVar(str) @@ -23,14 +23,12 @@ func (r *Runner) arithm(expr syntax.ArithmExpr) int { } // default to 0 return atoi(str) - case *syntax.ParamExp: - return atoi(r.paramExp(x)) case *syntax.ParenArithm: return r.arithm(x.X) case *syntax.UnaryArithm: switch x.Op { case syntax.Inc, syntax.Dec: - name := x.X.(*syntax.Lit).Value + name := x.X.(*syntax.Word).Parts[0].(*syntax.Lit).Value old := atoi(r.getVar(name)) val := old if x.Op == syntax.Inc { @@ -83,7 +81,7 @@ func atoi(s string) int { } func (r *Runner) assgnArit(b *syntax.BinaryArithm) int { - name := b.X.(*syntax.Lit).Value + name := b.X.(*syntax.Word).Parts[0].(*syntax.Lit).Value val := atoi(r.getVar(name)) arg := r.arithm(b.Y) switch b.Op { diff --git a/vendor/github.com/mvdan/sh/interp/interp.go b/vendor/github.com/mvdan/sh/interp/interp.go index 54f28ede..59113531 100644 --- a/vendor/github.com/mvdan/sh/interp/interp.go +++ b/vendor/github.com/mvdan/sh/interp/interp.go @@ -98,10 +98,12 @@ func (r *Runner) varInd(v varValue, e syntax.ArithmExpr) string { } case []string: // TODO: @ between double quotes - if lit, ok := e.(*syntax.Lit); ok { - switch lit.Value { - case "@", "*": - return strings.Join(x, " ") + if w, ok := e.(*syntax.Word); ok { + if lit, ok := w.Parts[0].(*syntax.Lit); ok { + switch lit.Value { + case "@", "*": + return strings.Join(x, " ") + } } } i := r.arithm(e) @@ -231,6 +233,9 @@ func (r *Runner) fields(words []*syntax.Word) []string { } func (r *Runner) loneWord(word *syntax.Word) string { + if word == nil { + return "" + } return strings.Join(r.wordParts(word.Parts, false), "") } @@ -263,13 +268,37 @@ func (r *Runner) stmt(st *syntax.Stmt) { } func (r *Runner) assignValue(as *syntax.Assign) varValue { + prev, _ := r.lookupVar(as.Name.Value) if as.Value != nil { - return r.loneWord(as.Value) + s := r.loneWord(as.Value) + if !as.Append || prev == nil { + return s + } + switch x := prev.(type) { + case string: + return x + s + case []string: + if len(x) == 0 { + return []string{s} + } + x[0] += s + return x + } + return s } if as.Array != nil { - strs := make([]string, len(as.Array.List)) - for i, w := range as.Array.List { - strs[i] = r.loneWord(w) + strs := make([]string, len(as.Array.Elems)) + for i, elem := range as.Array.Elems { + strs[i] = r.loneWord(elem.Value) + } + if !as.Append || prev == nil { + return strs + } + switch x := prev.(type) { + case string: + return append([]string{x}, strs...) + case []string: + return append(x, strs...) } return strs } @@ -279,16 +308,15 @@ func (r *Runner) assignValue(as *syntax.Assign) varValue { func (r *Runner) stmtSync(st *syntax.Stmt) { oldVars := r.cmdVars for _, as := range st.Assigns { - name := as.Name.Value val := r.assignValue(as) if st.Cmd == nil { - r.setVar(name, val) + r.setVar(as.Name.Value, val) continue } if r.cmdVars == nil { r.cmdVars = make(map[string]varValue, len(st.Assigns)) } - r.cmdVars[name] = val + r.cmdVars[as.Name.Value] = val } oldIn, oldOut, oldErr := r.Stdin, r.Stdout, r.Stderr for _, rd := range st.Redirs { @@ -392,7 +420,7 @@ func (r *Runner) cmd(cm syntax.Command) { switch y := x.Loop.(type) { case *syntax.WordIter: name := y.Name.Value - for _, field := range r.fields(y.List) { + for _, field := range r.fields(y.Items) { r.setVar(name, field) if r.loopStmtsBroken(x.DoStmts) { break @@ -423,11 +451,11 @@ func (r *Runner) cmd(cm syntax.Command) { } case *syntax.CaseClause: str := r.loneWord(x.Word) - for _, pl := range x.List { - for _, word := range pl.Patterns { + for _, ci := range x.Items { + for _, word := range ci.Patterns { pat := r.loneWord(word) if match(pat, str) { - r.stmts(pl.Stmts) + r.stmts(ci.Stmts) return } } @@ -436,6 +464,13 @@ func (r *Runner) cmd(cm syntax.Command) { if r.bashTest(x.X) == "" && r.exit == 0 { r.exit = 1 } + case *syntax.DeclClause: + if len(x.Opts) > 0 { + r.runErr(cm.Pos(), "unhandled declare opts") + } + for _, as := range x.Assigns { + r.setVar(as.Name.Value, r.assignValue(as)) + } default: r.runErr(cm.Pos(), "unhandled command node: %T", x) } diff --git a/vendor/github.com/mvdan/sh/interp/test.go b/vendor/github.com/mvdan/sh/interp/test.go index 2fc399e8..299f5995 100644 --- a/vendor/github.com/mvdan/sh/interp/test.go +++ b/vendor/github.com/mvdan/sh/interp/test.go @@ -146,7 +146,9 @@ func (r *Runner) unTest(op syntax.UnTestOperator, x string) bool { case syntax.TsNempStr: return x != "" //case syntax.TsOptSet: - //case syntax.TsVarSet: + case syntax.TsVarSet: + _, e := r.lookupVar(x) + return e //case syntax.TsRefVar: case syntax.TsNot: return x == "" diff --git a/vendor/github.com/mvdan/sh/syntax/canonical.sh b/vendor/github.com/mvdan/sh/syntax/canonical.sh index e67bd5b7..0d164d40 100644 --- a/vendor/github.com/mvdan/sh/syntax/canonical.sh +++ b/vendor/github.com/mvdan/sh/syntax/canonical.sh @@ -18,10 +18,10 @@ for foo in a b c; do done case $foo in - a) A ;; - b) - B - ;; +a) A ;; +b) + B + ;; esac foo | bar diff --git a/vendor/github.com/mvdan/sh/syntax/lexer.go b/vendor/github.com/mvdan/sh/syntax/lexer.go index f8016902..29ca97bf 100644 --- a/vendor/github.com/mvdan/sh/syntax/lexer.go +++ b/vendor/github.com/mvdan/sh/syntax/lexer.go @@ -32,7 +32,7 @@ func paramOps(r rune) bool { func arithmOps(r rune) bool { switch r { case '+', '-', '!', '*', '/', '%', '(', ')', '^', '<', '>', ':', '=', - ',', '?', '|', '&', ']': + ',', '?', '|', '&', '[', ']', '#': return true } return false @@ -86,10 +86,11 @@ retry: return p.r } -func (p *Parser) unrune(r rune) { +func (p *Parser) unrune(r rune, tok token) { if p.r != utf8.RuneSelf { p.npos -= utf8.RuneLen(p.r) p.r = r + p.tok = tok } } @@ -240,6 +241,13 @@ skipSpace: p.litBs = nil } p.next() + case '[': + if p.quote == arrayElems { + p.tok = leftBrack + p.rune() + } else { + p.advanceLitNone(r) + } case '?', '*', '+', '@', '!': if p.peekByte('(') { switch r { @@ -306,7 +314,7 @@ func (p *Parser) regToken(r rune) token { p.rune() return andAnd case '>': - if !p.bash() { + if p.lang == LangPOSIX { break } if p.rune() == '>' { @@ -322,23 +330,23 @@ func (p *Parser) regToken(r rune) token { p.rune() return orOr case '&': - if !p.bash() { + if p.lang == LangPOSIX { break } p.rune() - return pipeAll + return orAnd } return or case '$': switch p.rune() { case '\'': - if !p.bash() { + if p.lang == LangPOSIX { break } p.rune() return dollSglQuote case '"': - if !p.bash() { + if p.lang == LangPOSIX { break } p.rune() @@ -347,7 +355,7 @@ func (p *Parser) regToken(r rune) token { p.rune() return dollBrace case '[': - if !p.bash() { + if p.lang != LangBash { break } p.rune() @@ -361,7 +369,7 @@ func (p *Parser) regToken(r rune) token { } return dollar case '(': - if p.rune() == '(' && p.bash() { + if p.rune() == '(' && p.lang != LangPOSIX { p.rune() return dblLeftParen } @@ -372,17 +380,23 @@ func (p *Parser) regToken(r rune) token { case ';': switch p.rune() { case ';': - if p.rune() == '&' && p.bash() { + if p.rune() == '&' && p.lang == LangBash { p.rune() - return dblSemiFall + return dblSemiAnd } return dblSemicolon case '&': - if !p.bash() { + if p.lang == LangPOSIX { break } p.rune() - return semiFall + return semiAnd + case '|': + if p.lang != LangMirBSDKorn { + break + } + p.rune() + return semiOr } return semicolon case '<': @@ -391,7 +405,7 @@ func (p *Parser) regToken(r rune) token { if r = p.rune(); r == '-' { p.rune() return dashHdoc - } else if r == '<' && p.bash() { + } else if r == '<' && p.lang != LangPOSIX { p.rune() return wordHdoc } @@ -403,7 +417,7 @@ func (p *Parser) regToken(r rune) token { p.rune() return dplIn case '(': - if !p.bash() { + if p.lang != LangBash { break } p.rune() @@ -422,7 +436,7 @@ func (p *Parser) regToken(r rune) token { p.rune() return clbOut case '(': - if !p.bash() { + if p.lang != LangBash { break } p.rune() @@ -446,7 +460,7 @@ func (p *Parser) dqToken(r rune) token { p.rune() return dollBrace case '[': - if !p.bash() { + if p.lang != LangBash { break } p.rune() @@ -651,6 +665,9 @@ func (p *Parser) arithmToken(r rune) token { return xorAssgn } return caret + case '[': + p.rune() + return leftBrack case ']': p.rune() return rightBrack @@ -660,9 +677,12 @@ func (p *Parser) arithmToken(r rune) token { case '?': p.rune() return quest - default: // ':' + case ':': p.rune() return colon + default: // '#' + p.rune() + return hash } } @@ -756,7 +776,7 @@ loop: if p.quote&allParamReg != 0 { break loop } - if r == '[' && p.bash() && p.quote&allArithmExpr != 0 { + if r == '[' && p.lang != LangPOSIX && p.quote&allArithmExpr != 0 { break loop } case '+': @@ -829,7 +849,7 @@ loop: case '=': p.asPos = len(p.litBs) - 1 case '[': - if p.bash() && len(p.litBs) > 1 && p.litBs[0] != '[' { + if p.lang != LangPOSIX && len(p.litBs) > 1 && p.litBs[0] != '[' { tok = _Lit break loop } @@ -864,19 +884,25 @@ func (p *Parser) advanceLitHdoc(r rune) { } } lStart := len(p.litBs) - 1 -loop: - for ; r != utf8.RuneSelf; r = p.rune() { + for ; ; r = p.rune() { switch r { case '`', '$': - break loop + p.val = p.endLit() + return case '\\': // escaped byte follows p.rune() - case '\n': - if bytes.Equal(p.litBs[lStart:len(p.litBs)-1], p.hdocStop) { + case '\n', utf8.RuneSelf: + if bytes.HasPrefix(p.litBs[lStart:], p.hdocStop) { p.val = p.endLit()[:lStart] + if p.val == "" { + p.tok = illegalTok + } p.hdocStop = nil return } + if r == utf8.RuneSelf { + return + } if p.quote == hdocBodyTabs { for p.peekByte('\t') { p.rune() @@ -885,22 +911,15 @@ loop: lStart = len(p.litBs) } } - if bytes.Equal(p.litBs[lStart:], p.hdocStop) { - p.val = p.endLit()[:lStart] - p.hdocStop = nil - } else { - p.val = p.endLit() - } } func (p *Parser) hdocLitWord() *Word { r := p.r p.newLit(r) - pos, val := p.getPos(), "" - for { + pos := p.getPos() + for ; ; r = p.rune() { if r == utf8.RuneSelf { - val = p.endLit() - break + return nil } if p.quote == hdocBodyTabs { for r == '\t' { @@ -911,18 +930,15 @@ func (p *Parser) hdocLitWord() *Word { for r != utf8.RuneSelf && r != '\n' { r = p.rune() } - lEnd := len(p.litBs) - if r != utf8.RuneSelf { - lEnd-- + if bytes.HasPrefix(p.litBs[lStart:], p.hdocStop) { + p.hdocStop = nil + val := p.endLit()[:lStart] + if val == "" { + return nil + } + return p.word(p.wps(p.lit(pos, val))) } - if bytes.Equal(p.litBs[lStart:lEnd], p.hdocStop) { - val = p.endLit()[:lStart] - break - } - r = p.rune() } - l := p.lit(pos, val) - return p.word(p.wps(l)) } func (p *Parser) advanceLitRe(r rune) { diff --git a/vendor/github.com/mvdan/sh/syntax/nodes.go b/vendor/github.com/mvdan/sh/syntax/nodes.go index 65e8ee0d..0d7e9b47 100644 --- a/vendor/github.com/mvdan/sh/syntax/nodes.go +++ b/vendor/github.com/mvdan/sh/syntax/nodes.go @@ -111,12 +111,15 @@ func (c *Comment) End() Pos { return c.Hash + Pos(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. +// +// The Coprocess field is particular to MirBSDKorn. type Stmt struct { Cmd Command Position Pos Semicolon Pos Negated bool Background bool + Coprocess bool Assigns []*Assign Redirs []*Redirect } @@ -147,7 +150,7 @@ func (s *Stmt) End() Pos { // // These are *CallExpr, *IfClause, *WhileClause, *ForClause, // *CaseClause, *Block, *Subshell, *BinaryCmd, *FuncDecl, *ArithmCmd, -// *TestClause, *DeclClause, *LetClause, and *CoprocClause. +// *TestClause, *DeclClause, *LetClause, *TimeClause, and *CoprocClause. type Command interface { Node commandNode() @@ -166,24 +169,21 @@ func (*ArithmCmd) commandNode() {} func (*TestClause) commandNode() {} func (*DeclClause) commandNode() {} func (*LetClause) commandNode() {} +func (*TimeClause) commandNode() {} func (*CoprocClause) commandNode() {} // Assign represents an assignment to a variable. type Assign struct { Append bool + Naked bool Name *Lit Index ArithmExpr + Key *DblQuoted Value *Word Array *ArrayExpr } -func (a *Assign) Pos() Pos { - if a.Name != nil { - return a.Name.Pos() - } - return a.Value.Pos() -} - +func (a *Assign) Pos() Pos { return a.Name.Pos() } func (a *Assign) End() Pos { if a.Value != nil { return a.Value.End() @@ -194,6 +194,12 @@ func (a *Assign) End() Pos { if a.Index != nil { return a.Index.End() + 2 } + if a.Key != nil { + return a.Key.End() + 2 + } + if a.Naked { + return a.Name.End() + } return a.Name.End() + 1 } @@ -293,12 +299,12 @@ func (*CStyleLoop) loopNode() {} // WordIter represents the iteration of a variable over a series of // words in a for clause. type WordIter struct { - Name *Lit - List []*Word + Name *Lit + Items []*Word } func (w *WordIter) Pos() Pos { return w.Name.Pos() } -func (w *WordIter) End() Pos { return posMax(w.Name.End(), wordLastEnd(w.List)) } +func (w *WordIter) End() Pos { return posMax(w.Name.End(), wordLastEnd(w.Items)) } // CStyleLoop represents the behaviour of a for clause similar to the C // language. @@ -408,6 +414,11 @@ func (q *DblQuoted) End() Pos { type CmdSubst struct { Left, Right Pos Stmts []*Stmt + + // MirBSDTempFile is true for mksh's ${ foo;} + MirBSDTempFile bool + // MirBSDReplyvar is true for mksh's ${|foo;} + MirBSDReplyVar bool } func (c *CmdSubst) Pos() Pos { return c.Left } @@ -419,8 +430,10 @@ type ParamExp struct { Short bool Indirect bool Length bool + Width bool Param *Lit Index ArithmExpr + Key *DblQuoted Slice *Slice Repl *Replace Exp *Expansion @@ -431,10 +444,18 @@ func (p *ParamExp) End() Pos { if !p.Short { return p.Rbrace + 1 } + if p.Index != nil { + return p.Index.End() + 1 + } + if p.Key != nil { + return p.Key.End() + 1 + } return p.Param.End() } -func (p *ParamExp) nakedIndex() bool { return p.Short && p.Index != nil } +func (p *ParamExp) nakedIndex() bool { + return p.Short && (p.Index != nil || p.Key != nil) +} // Slice represents character slicing inside a ParamExp. // @@ -460,6 +481,7 @@ type Expansion struct { type ArithmExp struct { Left, Right Pos Bracket bool + Unsigned bool X ArithmExpr } @@ -476,6 +498,7 @@ func (a *ArithmExp) End() Pos { // This node will never appear when in PosixConformant mode. type ArithmCmd struct { Left, Right Pos + Unsigned bool X ArithmExpr } @@ -484,8 +507,7 @@ func (a *ArithmCmd) End() Pos { return a.Right + 2 } // ArithmExpr represents all nodes that form arithmetic expressions. // -// These are *BinaryArithm, *UnaryArithm, *ParenArithm, *Lit, and -// *ParamExp. +// These are *BinaryArithm, *UnaryArithm, *ParenArithm, and *Word. type ArithmExpr interface { Node arithmExprNode() @@ -494,14 +516,13 @@ type ArithmExpr interface { func (*BinaryArithm) arithmExprNode() {} func (*UnaryArithm) arithmExprNode() {} func (*ParenArithm) arithmExprNode() {} -func (*Lit) arithmExprNode() {} -func (*ParamExp) arithmExprNode() {} +func (*Word) arithmExprNode() {} // BinaryArithm represents a binary expression between two arithmetic // expression. // -// If Op is any assign operator, X will be a *Lit whose value is a valid -// name. +// If Op is any assign operator, X will be a word with a single *Lit +// whose value is a valid name. // // Ternary operators like "a ? b : c" are fit into this structure. Thus, // if Op == Quest, Y will be a *BinaryArithm with Op == Colon. Op can @@ -518,7 +539,8 @@ func (b *BinaryArithm) End() Pos { return b.Y.End() } // UnaryArithm represents an unary expression over a node, either before // or after it. // -// If Op is Inc or Dec, X will be a *Lit whose value is a valid name. +// If Op is Inc or Dec, X will be a word with a single *Lit whose value +// is a valid name. type UnaryArithm struct { OpPos Pos Op UnAritOperator @@ -554,14 +576,14 @@ func (p *ParenArithm) End() Pos { return p.Rparen + 1 } type CaseClause struct { Case, Esac Pos Word *Word - List []*PatternList + Items []*CaseItem } func (c *CaseClause) Pos() Pos { return c.Case } func (c *CaseClause) End() Pos { return c.Esac + 4 } -// PatternList represents a pattern list (case) within a CaseClause. -type PatternList struct { +// CaseItem represents a pattern list (case) within a CaseClause. +type CaseItem struct { Op CaseOperator OpPos Pos Patterns []*Word @@ -647,12 +669,29 @@ func (d *DeclClause) End() Pos { // This node will never appear when in PosixConformant mode. type ArrayExpr struct { Lparen, Rparen Pos - List []*Word + Elems []*ArrayElem } func (a *ArrayExpr) Pos() Pos { return a.Lparen } func (a *ArrayExpr) End() Pos { return a.Rparen + 1 } +type ArrayElem struct { + Index ArithmExpr + Key *DblQuoted + Value *Word +} + +func (a *ArrayElem) Pos() Pos { + if a.Index != nil { + return a.Index.Pos() + } + if a.Key != nil { + return a.Key.Pos() + } + return a.Value.Pos() +} +func (a *ArrayElem) End() Pos { return a.Value.End() } + // ExtGlob represents a Bash extended globbing expression. Note that // these are parsed independently of whether shopt has been called or // not. @@ -679,6 +718,22 @@ type ProcSubst struct { func (s *ProcSubst) Pos() Pos { return s.OpPos } func (s *ProcSubst) End() Pos { return s.Rparen + 1 } +// TimeClause represents a Bash time clause. +// +// This node will never appear when in PosixConformant mode. +type TimeClause struct { + Time Pos + Stmt *Stmt +} + +func (c *TimeClause) Pos() Pos { return c.Time } +func (c *TimeClause) End() Pos { + if c.Stmt == nil { + return c.Time + 4 + } + return c.Stmt.End() +} + // CoprocClause represents a Bash coproc clause. // // This node will never appear when in PosixConformant mode. diff --git a/vendor/github.com/mvdan/sh/syntax/parser.go b/vendor/github.com/mvdan/sh/syntax/parser.go index 3a2fd65f..70dcfdde 100644 --- a/vendor/github.com/mvdan/sh/syntax/parser.go +++ b/vendor/github.com/mvdan/sh/syntax/parser.go @@ -18,6 +18,7 @@ type LangVariant int const ( LangBash LangVariant = iota LangPOSIX + LangMirBSDKorn ) func Variant(l LangVariant) func(*Parser) { @@ -207,10 +208,12 @@ const ( paramExpLen paramExpRepl paramExpExp + arrayElems allKeepSpaces = paramExpRepl | dblQuotes | hdocBody | hdocBodyTabs | paramExpExp | sglQuotes - allRegTokens = noState | subCmd | subCmdBckquo | hdocWord | switchCase + allRegTokens = noState | subCmd | subCmdBckquo | hdocWord | + switchCase | arrayElems allArithmExpr = arithmExpr | arithmExprLet | arithmExprCmd | arithmExprBrack | allParamArith allRbrack = arithmExprBrack | paramExpInd | paramName @@ -219,8 +222,6 @@ const ( allParamExp = allParamReg | paramExpRepl | paramExpExp ) -func (p *Parser) bash() bool { return p.lang == LangBash } - type saveState struct { quote quoteState buriedHdocs int @@ -280,10 +281,9 @@ func (p *Parser) doHeredocs() { if p.err != nil { break } + p.quote = hdocBody if r.Op == DashHdoc { p.quote = hdocBodyTabs - } else { - p.quote = hdocBody } var quoted bool p.hdocStop, quoted = p.unquotedWordBytes(r.Word) @@ -294,7 +294,11 @@ func (p *Parser) doHeredocs() { r.Hdoc = p.hdocLitWord() } else { p.next() - r.Hdoc = p.getWordOrEmpty() + r.Hdoc = p.getWord() + } + if p.hdocStop != nil { + p.posErr(r.Pos(), "unclosed here-document '%s'", + string(p.hdocStop)) } } p.quote = old @@ -458,7 +462,7 @@ func (p *Parser) stmts(stops ...string) (sts []*Stmt) { if p.quote == subCmdBckquo { return } - case dblSemicolon, semiFall, dblSemiFall: + case dblSemicolon, semiAnd, dblSemiAnd, semiOr: if p.quote == switchCase { return } @@ -501,16 +505,6 @@ func (p *Parser) getWord() *Word { return nil } -func (p *Parser) getWordOrEmpty() *Word { - parts := p.wordParts() - if len(parts) == 0 { - l := p.lit(p.pos, "") - l.ValueEnd = l.ValuePos // force Lit.Pos() == Lit.End() - return p.word(p.wps(l)) - } - return p.word(parts) -} - func (p *Parser) getLit() *Lit { switch p.tok { case _Lit, _LitWord, _LitRedir: @@ -552,7 +546,34 @@ func (p *Parser) wordPart() WordPart { return l case dollBrace: p.ensureNoNested() - return p.paramExp() + switch p.r { + case '|': + if p.lang != LangMirBSDKorn { + p.curErr(`"${|stmts;}" is a mksh feature`) + } + fallthrough + case ' ', '\t', '\n': + if p.lang != LangMirBSDKorn { + p.curErr(`"${ stmts;}" is a mksh feature`) + } + cs := &CmdSubst{ + Left: p.pos, + MirBSDTempFile: p.r != '|', + MirBSDReplyVar: p.r == '|', + } + old := p.preNested(subCmd) + p.rune() // don't tokenize '|' + p.next() + cs.Stmts = p.stmts("}") + p.postNested(old) + cs.Right = p.pos + if !p.gotRsrv("}") { + p.matchingErr(cs.Left, "${", "}") + } + return cs + default: + return p.paramExp() + } case dollDblParen, dollBrack: p.ensureNoNested() left := p.tok @@ -564,7 +585,13 @@ func (p *Parser) wordPart() WordPart { old = p.preNested(arithmExpr) } p.next() - ar.X = p.arithmExpr(left, ar.Left, 0, false, false) + if p.got(hash) { + if p.lang != LangMirBSDKorn { + p.posErr(ar.Pos(), "unsigned expressions are a mksh feature") + } + ar.Unsigned = true + } + ar.X = p.followArithm(left, ar.Left) if ar.Bracket { if p.tok != rightBrack { p.matchingErr(ar.Left, dollBrack, rightBrack) @@ -598,6 +625,9 @@ func (p *Parser) wordPart() WordPart { ps.Rparen = p.matched(ps.OpPos, token(ps.Op), rightParen) return ps case sglQuote: + if p.quote&allArithmExpr != 0 { + p.curErr("quotes should not be used in arithmetic expressions") + } sq := &SglQuoted{Position: p.pos} r := p.r loop: @@ -615,6 +645,9 @@ func (p *Parser) wordPart() WordPart { p.next() return sq case dollSglQuote: + if p.quote&allArithmExpr != 0 { + p.curErr("quotes should not be used in arithmetic expressions") + } sq := &SglQuoted{Position: p.pos, Dollar: true} old := p.quote p.quote = sglQuotes @@ -634,16 +667,10 @@ func (p *Parser) wordPart() WordPart { } fallthrough case dollDblQuote: - q := &DblQuoted{Position: p.pos, Dollar: p.tok == dollDblQuote} - old := p.quote - p.quote = dblQuotes - p.next() - q.Parts = p.wordParts() - p.quote = old - if !p.got(dblQuote) { - p.quoteErr(q.Pos(), dblQuote) + if p.quote&allArithmExpr != 0 { + p.curErr("quotes should not be used in arithmetic expressions") } - return q + return p.dblQuoted() case bckQuote: if p.quote == subCmdBckquo { return nil @@ -660,7 +687,7 @@ func (p *Parser) wordPart() WordPart { } return cs case globQuest, globStar, globPlus, globAt, globExcl: - if !p.bash() { + if p.lang == LangPOSIX { p.curErr("extended globs are a bash feature") } eg := &ExtGlob{Op: GlobOperator(p.tok), OpPos: p.pos} @@ -691,6 +718,19 @@ func (p *Parser) wordPart() WordPart { } } +func (p *Parser) dblQuoted() *DblQuoted { + q := &DblQuoted{Position: p.pos, Dollar: p.tok == dollDblQuote} + old := p.quote + p.quote = dblQuotes + p.next() + q.Parts = p.wordParts() + p.quote = old + if !p.got(dblQuote) { + p.quoteErr(q.Pos(), dblQuote) + } + return q +} + func arithmOpLevel(op BinAritOperator) int { switch op { case Comma: @@ -722,6 +762,14 @@ func arithmOpLevel(op BinAritOperator) int { return -1 } +func (p *Parser) followArithm(ftok token, fpos Pos) ArithmExpr { + x := p.arithmExpr(ftok, fpos, 0, false, false) + if x == nil { + p.followErrExp(fpos, ftok.String()) + } + return x +} + func (p *Parser) arithmExpr(ftok token, fpos Pos, level int, compact, tern bool) ArithmExpr { if p.tok == _EOF || p.peekArithmEnd() { return nil @@ -771,7 +819,7 @@ func (p *Parser) arithmExpr(ftok token, fpos Pos, level int, compact, tern bool) } case AddAssgn, SubAssgn, MulAssgn, QuoAssgn, RemAssgn, AndAssgn, OrAssgn, XorAssgn, ShlAssgn, ShrAssgn, Assgn: - if l, ok := b.X.(*Lit); !ok || !validIdent(l.Value, p.bash()) { + if !isArithName(b.X) { p.posErr(b.OpPos, "%s must follow a name", b.Op.String()) } } @@ -790,6 +838,21 @@ func (p *Parser) arithmExpr(ftok token, fpos Pos, level int, compact, tern bool) return b } +func isArithName(left ArithmExpr) bool { + w, ok := left.(*Word) + if !ok || len(w.Parts) != 1 { + return false + } + switch x := w.Parts[0].(type) { + case *Lit: + return validIdent(x.Value) + case *ParamExp: + return x.nakedIndex() + default: + return false + } +} + func (p *Parser) arithmExprBase(compact bool) ArithmExpr { var x ArithmExpr switch p.tok { @@ -803,19 +866,15 @@ func (p *Parser) arithmExprBase(compact bool) ArithmExpr { case addAdd, subSub: ue := &UnaryArithm{OpPos: p.pos, Op: UnAritOperator(p.tok)} p.next() - if lit := p.getLit(); lit == nil { + if p.tok != _LitWord { p.followErr(ue.OpPos, token(ue.Op).String(), "a literal") - } else { - ue.X = lit } + ue.X = p.arithmExprBase(compact) return ue case leftParen: pe := &ParenArithm{Lparen: p.pos} p.next() - pe.X = p.arithmExpr(leftParen, pe.Lparen, 0, false, false) - if pe.X == nil { - p.posErr(pe.Lparen, "parentheses must enclose an expression") - } + pe.X = p.followArithm(leftParen, pe.Lparen) pe.Rparen = p.matched(pe.Lparen, leftParen, rightParen) x = pe case plus, minus: @@ -828,54 +887,45 @@ func (p *Parser) arithmExprBase(compact bool) ArithmExpr { p.followErrExp(ue.OpPos, ue.Op.String()) } x = ue - case illegalTok, rightBrack, rightBrace, rightParen: case _LitWord: l := p.getLit() - if p.r != '[' { - x = l + if p.tok != leftBrack { + x = p.word(p.wps(l)) break } + left := p.pos pe := &ParamExp{Dollar: l.ValuePos, Short: true, Param: l} - p.rune() - left := p.pos + 1 old := p.preNested(arithmExprBrack) p.next() - pe.Index = p.arithmExpr(leftBrack, left, 0, false, false) - if pe.Index == nil { - p.followErrExp(left, "[") + if p.tok == dblQuote { + pe.Key = p.dblQuoted() + } else { + pe.Index = p.followArithm(leftBrack, left) } p.postNested(old) p.matched(left, leftBrack, rightBrack) - x = pe + x = p.word(p.wps(pe)) case dollar: - x = p.shortParamExp() + x = p.word(p.wps(p.shortParamExp())) case dollBrace: - x = p.paramExp() + x = p.word(p.wps(p.paramExp())) case bckQuote: if p.quote == arithmExprLet { return nil } fallthrough default: - if arithmOpLevel(BinAritOperator(p.tok)) >= 0 { - break + if w := p.getWord(); w != nil { + // we want real nil, not (*Word)(nil) as that + // sets the type to non-nil and then x != nil + x = w } - p.curErr("arithmetic expressions must consist of names and numbers") } if compact && p.spaced { return x } if p.tok == addAdd || p.tok == subSub { - switch y := x.(type) { - case *Lit: - if !validIdent(y.Value, p.bash()) { - p.curErr("%s must follow a name", p.tok.String()) - } - case *ParamExp: - if !y.nakedIndex() { - p.curErr("%s must follow a name", p.tok.String()) - } - default: + if !isArithName(x) { p.curErr("%s must follow a name", p.tok.String()) } u := &UnaryArithm{ @@ -903,7 +953,7 @@ func (p *Parser) shortParamExp() *ParamExp { p.advanceLitOther(p.r) p.quote = old if p.val == "" || p.val == "\x80" { - p.posErr(pe.Dollar, "$ must be escaped or followed by a literal") + p.posErr(pe.Dollar, "$ literal must be escaped or single-quoted") } } pe.Param = p.getLit() @@ -919,14 +969,21 @@ func (p *Parser) paramExp() *ParamExp { case at: p.tok, p.val = _LitWord, "@" case dblHash: - p.tok = hash - p.unrune('#') + p.unrune('#', hash) fallthrough case hash: if p.r != '}' { pe.Length = true p.next() } + case perc: + if p.lang != LangMirBSDKorn { + p.posErr(pe.Pos(), `"${%%foo}" is a mksh feature`) + } + if p.r != '}' { + pe.Width = true + p.next() + } case exclMark: if p.r != '}' { pe.Indirect = true @@ -960,7 +1017,7 @@ func (p *Parser) paramExp() *ParamExp { return pe } if p.tok == leftBrack { - if !p.bash() { + if p.lang == LangPOSIX { p.curErr("arrays are a bash feature") } lpos := p.pos @@ -970,9 +1027,10 @@ func (p *Parser) paramExp() *ParamExp { case star, at: p.tok, p.val = _LitWord, p.tok.String() } - pe.Index = p.arithmExpr(leftBrack, lpos, 0, false, false) - if pe.Index == nil { - p.followErrExp(lpos, "[") + if p.tok == dblQuote { + pe.Key = p.dblQuoted() + } else { + pe.Index = p.followArithm(leftBrack, lpos) } p.quote = paramExpName p.matched(lpos, leftBrack, rightBrack) @@ -984,45 +1042,44 @@ func (p *Parser) paramExp() *ParamExp { p.next() return pe case slash, dblSlash: - if !p.bash() { + if p.lang == LangPOSIX { p.curErr("search and replace is a bash feature") } pe.Repl = &Replace{All: p.tok == dblSlash} p.quote = paramExpRepl p.next() - pe.Repl.Orig = p.getWordOrEmpty() + pe.Repl.Orig = p.getWord() + p.quote = paramExpExp switch p.tok { case dblSlash: - p.unrune('/') - fallthrough + p.unrune('/', slash) + p.next() case slash: - p.quote = paramExpExp p.next() } - pe.Repl.With = p.getWordOrEmpty() + pe.Repl.With = p.getWord() case colon: - if !p.bash() { + if p.lang == LangPOSIX { p.curErr("slicing is a bash feature") } pe.Slice = &Slice{} colonPos := p.pos p.quote = paramExpOff if p.next(); p.tok != colon { - pe.Slice.Offset = p.arithmExpr(colon, colonPos, 0, false, false) - if pe.Slice.Offset == nil { - p.followErrExp(colonPos, ":") - } + pe.Slice.Offset = p.followArithm(colon, colonPos) } colonPos = p.pos p.quote = paramExpLen if p.got(colon) { - pe.Slice.Length = p.arithmExpr(colon, colonPos, 0, false, false) - if pe.Slice.Length == nil { - p.followErrExp(colonPos, ":") - } + pe.Slice.Length = p.followArithm(colon, colonPos) } - case caret, dblCaret, comma, dblComma, at: - if !p.bash() { + case caret, dblCaret, comma, dblComma: + if p.lang != LangBash { + p.curErr("this expansion operator is a bash feature") + } + fallthrough + case at: + if p.lang == LangPOSIX { p.curErr("this expansion operator is a bash feature") } fallthrough @@ -1030,7 +1087,7 @@ func (p *Parser) paramExp() *ParamExp { pe.Exp = &Expansion{Op: ParExpOperator(p.tok)} p.quote = paramExpExp p.next() - pe.Exp.Word = p.getWordOrEmpty() + pe.Exp.Word = p.getWord() } p.quote = old pe.Rbrace = p.pos @@ -1043,11 +1100,10 @@ func (p *Parser) peekArithmEnd() bool { } func (p *Parser) arithmEnd(ltok token, lpos Pos, old saveState) Pos { - if p.peekArithmEnd() { - p.rune() - } else { + if !p.peekArithmEnd() { p.matchingErr(lpos, ltok, dblRightParen) } + p.rune() p.postNested(old) pos := p.pos p.next() @@ -1056,22 +1112,20 @@ func (p *Parser) arithmEnd(ltok token, lpos Pos, old saveState) Pos { func stopToken(tok token) bool { switch tok { - case _EOF, semicolon, and, or, andAnd, orOr, pipeAll, dblSemicolon, - semiFall, dblSemiFall, rightParen: + case _EOF, semicolon, and, or, andAnd, orOr, orAnd, dblSemicolon, + semiAnd, dblSemiAnd, semiOr, rightParen: return true } return false } -func validIdent(val string, bash bool) bool { +func validIdent(val string) bool { for i, c := range val { switch { case 'a' <= c && c <= 'z': case 'A' <= c && c <= 'Z': case c == '_': case i > 0 && '0' <= c && c <= '9': - case i > 0 && (c == '[' || c == ']') && bash: - case c == '+' && i == len(val)-1 && bash: default: return false } @@ -1080,17 +1134,22 @@ func validIdent(val string, bash bool) bool { } func (p *Parser) hasValidIdent() bool { - if p.asPos > 0 && validIdent(p.val[:p.asPos], p.bash()) { - return true + if end := p.asPos; end > 0 { + if p.val[end-1] == '+' && p.lang != LangPOSIX { + end-- + } + if validIdent(p.val[:end]) { + return true + } } return p.tok == _Lit && p.r == '[' } -func (p *Parser) getAssign() *Assign { +func (p *Parser) getAssign(needEqual bool) *Assign { as := &Assign{} if p.asPos > 0 { // foo=bar nameEnd := p.asPos - if p.bash() && p.val[p.asPos-1] == '+' { + if p.lang != LangPOSIX && p.val[p.asPos-1] == '+' { // a+=b as.Append = true nameEnd-- @@ -1103,24 +1162,38 @@ func (p *Parser) getAssign() *Assign { left.ValuePos += Pos(p.asPos) as.Value = p.word(p.wps(left)) } - if p.next(); p.spaced { - return as - } - } else { // foo[i]=bar + p.next() + } else { // foo[x]=bar as.Name = p.lit(p.pos, p.val) // hasValidIdent already checks p.r is '[' p.rune() left := p.pos + 1 old := p.preNested(arithmExprBrack) p.next() - as.Index = p.arithmExpr(leftBrack, left, 0, false, false) - if as.Index == nil { - p.followErrExp(left, "[") + if p.tok == star { + p.tok, p.val = _LitWord, p.tok.String() + } + if p.tok == dblQuote { + as.Key = p.dblQuoted() + } else { + as.Index = p.followArithm(leftBrack, left) } p.postNested(old) p.matched(left, leftBrack, rightBrack) - if p.tok == _EOF || p.val[0] != '=' { - p.followErr(as.Pos(), "a[b]", "=") + if !needEqual && (p.spaced || stopToken(p.tok)) { + return as + } + if len(p.val) > 0 && p.val[0] == '+' { + as.Append = true + p.val = p.val[1:] + p.pos++ + } + if len(p.val) < 1 || p.val[0] != '=' { + if as.Append { + p.followErr(as.Pos(), "a[b]+", "=") + } else { + p.followErr(as.Pos(), "a[b]", "=") + } return nil } p.pos++ @@ -1129,27 +1202,53 @@ func (p *Parser) getAssign() *Assign { p.next() } } + if p.spaced || stopToken(p.tok) { + return as + } if as.Value == nil && p.tok == leftParen { - if !p.bash() { + if p.lang == LangPOSIX { p.curErr("arrays are a bash feature") } as.Array = &ArrayExpr{Lparen: p.pos} + newQuote := p.quote + if p.lang == LangBash { + newQuote = arrayElems + } + old := p.preNested(newQuote) p.next() for p.tok != _EOF && p.tok != rightParen { - if w := p.getWord(); w == nil { - p.curErr("array elements must be words") - } else { - as.Array.List = append(as.Array.List, w) + ae := &ArrayElem{} + if p.tok == leftBrack { + left := p.pos + p.quote = arithmExprBrack + p.next() + if p.tok == dblQuote { + ae.Key = p.dblQuoted() + } else { + ae.Index = p.followArithm(leftBrack, left) + } + if p.tok != rightBrack { + p.matchingErr(left, leftBrack, rightBrack) + } + p.quote = arrayElems + if p.r != '=' { + p.followErr(left, `"[x]"`, "=") + } + p.rune() + p.next() } + if ae.Value = p.getWord(); ae.Value == nil { + p.curErr("array element values must be words") + } + as.Array.Elems = append(as.Array.Elems, ae) } + p.postNested(old) as.Array.Rparen = p.matched(as.Array.Lparen, leftParen, rightParen) - } else if !p.newLine && !stopToken(p.tok) { - if w := p.getWord(); w != nil { - if as.Value == nil { - as.Value = w - } else { - as.Value.Parts = append(as.Value.Parts, w.Parts...) - } + } else if w := p.getWord(); w != nil { + if as.Value == nil { + as.Value = w + } else { + as.Value.Parts = append(as.Value.Parts, w.Parts...) } } return as @@ -1192,32 +1291,8 @@ func (p *Parser) getStmt(readEnd, binCmd bool) (s *Stmt, gotEnd bool) { s = p.stmt(p.pos) if p.gotRsrv("!") { s.Negated = true - } -preLoop: - for { - switch p.tok { - case _Lit, _LitWord: - if p.hasValidIdent() { - s.Assigns = append(s.Assigns, p.getAssign()) - } else { - break preLoop - } - 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: - return - case p.tok == semicolon: - if readEnd { - s.Semicolon = p.pos - p.next() - gotEnd = true - } - return + if p.newLine || stopToken(p.tok) { + p.posErr(s.Pos(), `! cannot form a statement alone`) } } if s = p.gotStmtPipe(s); s == nil { @@ -1244,24 +1319,46 @@ preLoop: s = p.stmt(s.Position) s.Cmd = b } - if readEnd && p.gotSameLine(semicolon) { - gotEnd = true + if p.tok != semicolon { + break } - case and: - p.next() - s.Background = true - gotEnd = true + fallthrough case semicolon: if !p.newLine && readEnd { s.Semicolon = p.pos p.next() - gotEnd = true } + case and: + p.next() + s.Background = true + case orAnd: + p.next() + s.Coprocess = true } + gotEnd = s.Semicolon.IsValid() || s.Background || s.Coprocess return } 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 { @@ -1277,11 +1374,6 @@ func (p *Parser) gotStmtPipe(s *Stmt) *Stmt { s.Cmd = p.caseClause() case "}": p.curErr(`%s can only be used to close a block`, p.val) - case "]]": - if !p.bash() { - break - } - p.curErr(`%s can only be used to close a test`, p.val) case "then": p.curErr(`%q can only be used in an if`, p.val) case "elif": @@ -1294,28 +1386,54 @@ func (p *Parser) gotStmtPipe(s *Stmt) *Stmt { p.curErr(`%q can only be used to end a loop`, p.val) case "esac": p.curErr(`%q can only be used to end a case`, p.val) - default: - if !p.bash() { + case "[[": + if p.lang == LangPOSIX { break } - switch p.val { - case "[[": - s.Cmd = p.testClause() - case "declare", "local", "export", "readonly", - "typeset", "nameref": - s.Cmd = p.declClause() - case "coproc": - s.Cmd = p.coprocClause() - case "let": - s.Cmd = p.letClause() - case "function": - s.Cmd = p.bashFuncDecl() + s.Cmd = p.testClause() + case "]]": + if p.lang == LangPOSIX { + break } + p.curErr(`%s can only be used to close a test`, p.val) + case "let": + if p.lang == LangPOSIX { + break + } + s.Cmd = p.letClause() + case "function": + if p.lang == LangPOSIX { + break + } + s.Cmd = p.bashFuncDecl() + case "declare": + if p.lang != LangBash { + break + } + s.Cmd = p.declClause() + case "local", "export", "readonly", "typeset", "nameref": + if p.lang == LangPOSIX { + break + } + s.Cmd = p.declClause() + case "time": + if p.lang == LangPOSIX { + break + } + s.Cmd = p.timeClause() + case "coproc": + if p.lang != LangBash { + break + } + s.Cmd = p.coprocClause() } if s.Cmd == nil { name := p.lit(p.pos, p.val) if p.next(); p.gotSameLine(leftParen) { p.follow(name.ValuePos, "foo(", rightParen) + if p.lang == LangPOSIX && !validIdent(name.Value) { + p.posErr(name.Pos(), "invalid func name") + } s.Cmd = p.funcDecl(name, name.ValuePos) } else { s.Cmd = p.callExpr(s, p.word(p.wps(name))) @@ -1338,14 +1456,21 @@ func (p *Parser) gotStmtPipe(s *Stmt) *Stmt { s.Cmd = p.subshell() case dblLeftParen: s.Cmd = p.arithmExpCmd() + default: + if len(s.Redirs) == 0 && len(s.Assigns) == 0 { + return nil + } } for !p.newLine && p.peekRedir() { p.doRedirect(s) } - if s.Cmd == nil && len(s.Redirs) == 0 && !s.Negated && len(s.Assigns) == 0 { - return nil - } - if p.tok == or || p.tok == pipeAll { + switch p.tok { + case orAnd: + if p.lang == LangMirBSDKorn { + break + } + fallthrough + case or: b := &BinaryCmd{OpPos: p.pos, Op: BinCmdOperator(p.tok), X: s} p.next() if b.Y = p.gotStmtPipe(p.stmt(p.pos)); b.Y == nil { @@ -1371,7 +1496,13 @@ func (p *Parser) arithmExpCmd() Command { ar := &ArithmCmd{Left: p.pos} old := p.preNested(arithmExprCmd) p.next() - ar.X = p.arithmExpr(dblLeftParen, ar.Left, 0, false, false) + if p.got(hash) { + if p.lang != LangMirBSDKorn { + p.posErr(ar.Pos(), "unsigned expressions are a mksh feature") + } + ar.Unsigned = true + } + ar.X = p.followArithm(dblLeftParen, ar.Left) ar.Right = p.arithmEnd(dblLeftParen, ar.Left, old) return ar } @@ -1436,20 +1567,24 @@ func (p *Parser) forClause() *ForClause { } func (p *Parser) loop(forPos Pos) Loop { + if p.lang != LangBash { + switch p.tok { + case leftParen, dblLeftParen: + p.curErr("c-style fors are a bash feature") + } + } if p.tok == dblLeftParen { cl := &CStyleLoop{Lparen: p.pos} old := p.preNested(arithmExprCmd) if p.next(); p.tok == dblSemicolon { - p.unrune(';') - p.tok = semicolon + p.unrune(';', semicolon) } if p.tok != semicolon { cl.Init = p.arithmExpr(dblLeftParen, cl.Lparen, 0, false, false) } scPos := p.pos if p.tok == dblSemicolon { - p.unrune(';') - p.tok = semicolon + p.unrune(';', semicolon) } p.follow(p.pos, "expression", semicolon) if p.tok != semicolon { @@ -1473,7 +1608,7 @@ func (p *Parser) loop(forPos Pos) Loop { if w := p.getWord(); w == nil { p.curErr("word list can only contain words") } else { - wi.List = append(wi.List, w) + wi.Items = append(wi.Items, w) } } p.gotSameLine(semicolon) @@ -1487,21 +1622,29 @@ func (p *Parser) caseClause() *CaseClause { cc := &CaseClause{Case: p.pos} p.next() cc.Word = p.followWord("case", cc.Case) - p.followRsrv(cc.Case, "case x", "in") - cc.List = p.patLists() - cc.Esac = p.stmtEnd(cc, "case", "esac") + if p.gotRsrv("{") { + if p.lang != LangMirBSDKorn { + p.posErr(cc.Pos(), `"case i {" is a mksh feature`) + } + cc.Items = p.caseItems("}") + cc.Esac = p.stmtEnd(cc, "case", "}") + } else { + p.followRsrv(cc.Case, "case x", "in") + cc.Items = p.caseItems("esac") + cc.Esac = p.stmtEnd(cc, "case", "esac") + } return cc } -func (p *Parser) patLists() (pls []*PatternList) { - for p.tok != _EOF && !(p.tok == _LitWord && p.val == "esac") { - pl := &PatternList{} +func (p *Parser) caseItems(stop string) (items []*CaseItem) { + for p.tok != _EOF && !(p.tok == _LitWord && p.val == stop) { + ci := &CaseItem{} p.got(leftParen) for p.tok != _EOF { if w := p.getWord(); w == nil { p.curErr("case patterns must consist of words") } else { - pl.Patterns = append(pl.Patterns, w) + ci.Patterns = append(ci.Patterns, w) } if p.tok == rightParen { break @@ -1512,17 +1655,19 @@ func (p *Parser) patLists() (pls []*PatternList) { } old := p.preNested(switchCase) p.next() - pl.Stmts = p.stmts("esac") + ci.Stmts = p.stmts(stop) p.postNested(old) - pl.OpPos = p.pos - if p.tok != dblSemicolon && p.tok != semiFall && p.tok != dblSemiFall { - pl.Op = DblSemicolon - pls = append(pls, pl) - break + ci.OpPos = p.pos + switch p.tok { + case dblSemicolon, semiAnd, dblSemiAnd, semiOr: + default: + ci.Op = Break + items = append(items, ci) + return } - pl.Op = CaseOperator(p.tok) + ci.Op = CaseOperator(p.tok) p.next() - pls = append(pls, pl) + items = append(items, ci) } return } @@ -1587,6 +1732,9 @@ func (p *Parser) testExpr(ftok token, fpos Pos, level int) TestExpr { p.followErrExp(b.OpPos, b.Op.String()) } case TsReMatch: + if p.lang != LangBash { + p.curErr("regex tests are a bash feature") + } old := p.preNested(testRegexp) defer p.postNested(old) fallthrough @@ -1606,7 +1754,15 @@ func (p *Parser) testExprBase(ftok token, fpos Pos) TestExpr { case _EOF: return nil case _LitWord: - if op := testUnaryOp(p.val); op != illegalTok { + op := testUnaryOp(p.val) + switch op { + case illegalTok: + case tsRefVar, tsModif: + // TODO: check with man mksh + if p.lang == LangBash { + p.tok = op + } + default: p.tok = op } } @@ -1628,7 +1784,7 @@ func (p *Parser) testExprBase(ftok token, fpos Pos) TestExpr { pe := &ParenTest{Lparen: p.pos} p.next() if pe.X = p.testExpr(leftParen, pe.Lparen, 0); pe.X == nil { - p.posErr(pe.Lparen, "parentheses must enclose an expression") + p.followErrExp(pe.Lparen, "(") } pe.Rparen = p.matched(pe.Lparen, leftParen, rightParen) return pe @@ -1645,24 +1801,21 @@ func (p *Parser) testExprBase(ftok token, fpos Pos) TestExpr { } func (p *Parser) declClause() *DeclClause { - name := p.val - ds := &DeclClause{Position: p.pos} - switch name { - case "declare", "typeset": // typeset is an obsolete synonym - default: - ds.Variant = name - } + ds := &DeclClause{Position: p.pos, Variant: p.val} p.next() - for p.tok == _LitWord && p.val[0] == '-' { + for (p.tok == _LitWord || p.tok == _Lit) && p.val[0] == '-' { ds.Opts = append(ds.Opts, p.getWord()) } for !p.newLine && !stopToken(p.tok) && !p.peekRedir() { if (p.tok == _Lit || p.tok == _LitWord) && p.hasValidIdent() { - ds.Assigns = append(ds.Assigns, p.getAssign()) - } else if w := p.getWord(); w == nil { - p.followErr(p.pos, name, "words") + ds.Assigns = append(ds.Assigns, p.getAssign(false)) + } else if p.tok == _LitWord { + ds.Assigns = append(ds.Assigns, &Assign{ + Naked: true, + Name: p.getLit(), + }) } else { - ds.Assigns = append(ds.Assigns, &Assign{Value: w}) + p.followErr(p.pos, ds.Variant, "names or assignments") } } return ds @@ -1683,18 +1836,27 @@ func isBashCompoundCommand(tok token, val string) bool { return false } +func (p *Parser) timeClause() *TimeClause { + tc := &TimeClause{Time: p.pos} + p.next() + if !p.newLine { + tc.Stmt = p.gotStmtPipe(p.stmt(p.pos)) + } + return tc +} + func (p *Parser) coprocClause() *CoprocClause { cc := &CoprocClause{Coproc: p.pos} if p.next(); isBashCompoundCommand(p.tok, p.val) { // has no name - cc.Stmt, _ = p.getStmt(false, false) + cc.Stmt = p.gotStmtPipe(p.stmt(p.pos)) return cc } if p.newLine { p.posErr(cc.Coproc, "coproc clause requires a command") } cc.Name = p.getLit() - cc.Stmt, _ = p.getStmt(false, false) + cc.Stmt = p.gotStmtPipe(p.stmt(p.pos)) if cc.Stmt == nil { if cc.Name == nil { p.posErr(cc.Coproc, "coproc clause requires a command") @@ -1727,7 +1889,7 @@ func (p *Parser) letClause() *LetClause { lc.Exprs = append(lc.Exprs, x) } if len(lc.Exprs) == 0 { - p.posErr(lc.Let, "let clause requires at least one expression") + p.followErrExp(lc.Let, "let") } p.postNested(old) if p.tok == illegalTok { @@ -1755,8 +1917,8 @@ func (p *Parser) callExpr(s *Stmt, w *Word) *CallExpr { ce := p.call(w) for !p.newLine { switch p.tok { - case _EOF, semicolon, and, or, andAnd, orOr, pipeAll, - dblSemicolon, semiFall, dblSemiFall: + case _EOF, semicolon, and, or, andAnd, orOr, orAnd, + dblSemicolon, semiAnd, dblSemiAnd, semiOr: return ce case _LitWord: ce.Args = append(ce.Args, p.word( diff --git a/vendor/github.com/mvdan/sh/syntax/printer.go b/vendor/github.com/mvdan/sh/syntax/printer.go index f34923e9..dd95e6a2 100644 --- a/vendor/github.com/mvdan/sh/syntax/printer.go +++ b/vendor/github.com/mvdan/sh/syntax/printer.go @@ -34,18 +34,14 @@ func (p *Printer) Print(w io.Writer, f *File) error { p.stmts(f.Stmts) p.commentsUpTo(0) p.newline(0) - if flusher, ok := p.bufWriter.(interface { - Flush() error - }); ok { - return flusher.Flush() - } - return nil + return p.bufWriter.Flush() } type bufWriter interface { WriteByte(byte) error WriteString(string) (int, error) Reset(io.Writer) + Flush() error } type Printer struct { @@ -190,8 +186,10 @@ func (p *Printer) newline(pos Pos) { hdocs := p.pendingHdocs p.pendingHdocs = p.pendingHdocs[:0] for _, r := range hdocs { - p.word(r.Hdoc) - p.incLines(r.Hdoc.End()) + if r.Hdoc != nil { + p.word(r.Hdoc) + p.incLines(r.Hdoc.End()) + } p.unquotedWord(r.Word) p.WriteByte('\n') p.incLine() @@ -233,13 +231,13 @@ func (p *Printer) semiRsrv(s string, pos Pos, fallback bool) { p.level-- if p.wantNewline || pos > p.nline { p.newlines(pos) - } else if fallback { - if !p.wroteSemi { + } else { + if fallback && !p.wroteSemi { p.WriteByte(';') } - p.WriteByte(' ') - } else if p.wantSpace { - p.WriteByte(' ') + if p.wantSpace { + p.WriteByte(' ') + } } p.WriteString(s) p.wantSpace = true @@ -287,27 +285,34 @@ func (p *Printer) wordPart(wp WordPart) { p.WriteByte('\'') p.incLines(x.End()) case *DblQuoted: - if x.Dollar { - p.WriteByte('$') - } - p.WriteByte('"') - for i, n := range x.Parts { - p.wordPart(n) - if i == len(x.Parts)-1 { - p.incLines(n.End()) - } - } - p.WriteByte('"') + p.dblQuoted(x) case *CmdSubst: p.incLines(x.Pos()) - p.WriteString("$(") - p.wantSpace = len(x.Stmts) > 0 && startsWithLparen(x.Stmts[0]) - p.nestedStmts(x.Stmts, x.Right) - p.sepTok(")", x.Right) + switch { + case x.MirBSDTempFile: + p.WriteString("${") + p.wantSpace = true + p.nestedStmts(x.Stmts, x.Right) + p.wantSpace = false + p.semiRsrv("}", x.Right, true) + case x.MirBSDReplyVar: + p.WriteString("${|") + p.nestedStmts(x.Stmts, x.Right) + p.wantSpace = false + p.semiRsrv("}", x.Right, true) + default: + p.WriteString("$(") + p.wantSpace = len(x.Stmts) > 0 && startsWithLparen(x.Stmts[0]) + p.nestedStmts(x.Stmts, x.Right) + p.sepTok(")", x.Right) + } case *ParamExp: p.paramExp(x) case *ArithmExp: p.WriteString("$((") + if x.Unsigned { + p.WriteString("# ") + } p.arithmExpr(x.X, false, false) p.WriteString("))") case *ExtGlob: @@ -326,12 +331,38 @@ func (p *Printer) wordPart(wp WordPart) { } } +func (p *Printer) dblQuoted(dq *DblQuoted) { + if dq.Dollar { + p.WriteByte('$') + } + p.WriteByte('"') + for i, n := range dq.Parts { + p.wordPart(n) + if i == len(dq.Parts)-1 { + p.incLines(n.End()) + } + } + p.WriteByte('"') +} + +func (p *Printer) wroteIndex(index ArithmExpr, key *DblQuoted) bool { + if index == nil && key == nil { + return false + } + p.WriteByte('[') + if index != nil { + p.arithmExpr(index, false, false) + } else { + p.dblQuoted(key) + } + p.WriteByte(']') + return true +} + func (p *Printer) paramExp(pe *ParamExp) { - if pe.nakedIndex() { // arr[i] + if pe.nakedIndex() { // arr[x] p.WriteString(pe.Param.Value) - p.WriteByte('[') - p.arithmExpr(pe.Index, false, false) - p.WriteByte(']') + p.wroteIndex(pe.Index, pe.Key) return } if pe.Short { // $var @@ -344,17 +375,15 @@ func (p *Printer) paramExp(pe *ParamExp) { switch { case pe.Length: p.WriteByte('#') + case pe.Width: + p.WriteByte('%') case pe.Indirect: p.WriteByte('!') } if pe.Param != nil { p.WriteString(pe.Param.Value) } - if pe.Index != nil { - p.WriteByte('[') - p.arithmExpr(pe.Index, false, false) - p.WriteByte(']') - } + p.wroteIndex(pe.Index, pe.Key) if pe.Slice != nil { p.WriteByte(':') p.arithmExpr(pe.Slice.Offset, true, true) @@ -367,12 +396,18 @@ func (p *Printer) paramExp(pe *ParamExp) { p.WriteByte('/') } p.WriteByte('/') - p.word(pe.Repl.Orig) + if pe.Repl.Orig != nil { + p.word(pe.Repl.Orig) + } p.WriteByte('/') - p.word(pe.Repl.With) + if pe.Repl.With != nil { + p.word(pe.Repl.With) + } } else if pe.Exp != nil { p.WriteString(pe.Exp.Op.String()) - p.word(pe.Exp.Word) + if pe.Exp.Word != nil { + p.word(pe.Exp.Word) + } } p.WriteByte('}') } @@ -381,9 +416,9 @@ func (p *Printer) loop(loop Loop) { switch x := loop.(type) { case *WordIter: p.WriteString(x.Name.Value) - if len(x.List) > 0 { + if len(x.Items) > 0 { p.spacedString(" in") - p.wordJoin(x.List, true) + p.wordJoin(x.Items) } case *CStyleLoop: p.WriteString("((") @@ -401,10 +436,8 @@ func (p *Printer) loop(loop Loop) { func (p *Printer) arithmExpr(expr ArithmExpr, compact, spacePlusMinus bool) { switch x := expr.(type) { - case *Lit: - p.WriteString(x.Value) - case *ParamExp: - p.paramExp(x) + case *Word: + p.word(x) case *BinaryArithm: if compact { p.arithmExpr(x.X, compact, spacePlusMinus) @@ -491,17 +524,12 @@ func (p *Printer) unquotedWord(w *Word) { } } -func (p *Printer) wordJoin(ws []*Word, backslash bool) { +func (p *Printer) wordJoin(ws []*Word) { anyNewline := false for _, w := range ws { if pos := w.Pos(); pos > p.nline { p.commentsUpTo(pos) - if backslash { - p.bslashNewl() - } else { - p.WriteByte('\n') - p.incLine() - } + p.bslashNewl() if !anyNewline { p.incLevel() anyNewline = true @@ -518,11 +546,37 @@ func (p *Printer) wordJoin(ws []*Word, backslash bool) { } } +func (p *Printer) elemJoin(elems []*ArrayElem) { + anyNewline := false + for _, el := range elems { + if pos := el.Pos(); pos > p.nline { + p.commentsUpTo(pos) + p.WriteByte('\n') + p.incLine() + if !anyNewline { + p.incLevel() + anyNewline = true + } + p.indent() + } else if p.wantSpace { + p.WriteByte(' ') + p.wantSpace = false + } + if p.wroteIndex(el.Index, el.Key) { + p.WriteByte('=') + } + p.word(el.Value) + } + if anyNewline { + p.decLevel() + } +} + func (p *Printer) stmt(s *Stmt) { if s.Negated { p.spacedString("!") } - p.assigns(s.Assigns) + p.assigns(s.Assigns, true) var startRedirs int if s.Cmd != nil { startRedirs = p.command(s.Cmd, s.Redirs) @@ -545,21 +599,25 @@ func (p *Printer) stmt(s *Stmt) { p.WriteString(r.N.Value) } p.WriteString(r.Op.String()) + p.wantSpace = true p.word(r.Word) if r.Op == Hdoc || r.Op == DashHdoc { p.pendingHdocs = append(p.pendingHdocs, r) } } p.wroteSemi = false - if s.Semicolon.IsValid() && s.Semicolon > p.nline { + switch { + case s.Semicolon.IsValid() && s.Semicolon > p.nline: p.incLevel() p.bslashNewl() p.indent() p.decLevel() p.WriteByte(';') p.wroteSemi = true - } else if s.Background { + case s.Background: p.WriteString(" &") + case s.Coprocess: + p.WriteString(" |&") } if anyNewline { p.decLevel() @@ -574,10 +632,10 @@ func (p *Printer) command(cmd Command, redirs []*Redirect) (startRedirs int) { switch x := cmd.(type) { case *CallExpr: if len(x.Args) <= 1 { - p.wordJoin(x.Args, true) + p.wordJoin(x.Args) return 0 } - p.wordJoin(x.Args[:1], true) + p.wordJoin(x.Args[:1]) for _, r := range redirs { if r.Pos() > x.Args[1].Pos() || r.Op == Hdoc || r.Op == DashHdoc { break @@ -589,10 +647,11 @@ func (p *Printer) command(cmd Command, redirs []*Redirect) (startRedirs int) { p.WriteString(r.N.Value) } p.WriteString(r.Op.String()) + p.wantSpace = true p.word(r.Word) startRedirs++ } - p.wordJoin(x.Args[1:], true) + p.wordJoin(x.Args[1:]) case *Block: p.WriteByte('{') p.wantSpace = true @@ -639,13 +698,18 @@ func (p *Printer) command(cmd Command, redirs []*Redirect) (startRedirs int) { p.semiRsrv("done", x.Done, true) case *BinaryCmd: p.stmt(x.X) + if x.Y.Pos() < p.nline { + // leave p.nestedBinary untouched + p.spacedString(x.Op.String()) + p.stmt(x.Y) + break + } indent := !p.nestedBinary if indent { p.incLevel() } - _, p.nestedBinary = x.Y.Cmd.(*BinaryCmd) if p.binNextLine { - if len(p.pendingHdocs) == 0 && x.Y.Pos() > p.nline { + if len(p.pendingHdocs) == 0 { p.bslashNewl() p.indent() } @@ -662,16 +726,15 @@ func (p *Printer) command(cmd Command, redirs []*Redirect) (startRedirs int) { } else { p.wantSpace = true p.spacedString(x.Op.String()) - if x.Y.Pos() > p.nline { - if x.OpPos > p.nline { - p.incLines(x.OpPos) - } - p.commentsUpTo(x.Y.Pos()) - p.newline(0) - p.indent() + if x.OpPos > p.nline { + p.incLines(x.OpPos) } + p.commentsUpTo(x.Y.Pos()) + p.newline(0) + p.indent() } p.incLines(x.Y.Pos()) + _, p.nestedBinary = x.Y.Cmd.(*BinaryCmd) p.stmt(x.Y) if indent { p.decLevel() @@ -689,10 +752,9 @@ func (p *Printer) command(cmd Command, redirs []*Redirect) (startRedirs int) { p.WriteString("case ") p.word(x.Word) p.WriteString(" in") - p.incLevel() - for _, pl := range x.List { - p.commentsAndSeparate(pl.Patterns[0].Pos()) - for i, w := range pl.Patterns { + for _, ci := range x.Items { + p.commentsAndSeparate(ci.Patterns[0].Pos()) + for i, w := range ci.Patterns { if i > 0 { p.spacedString("|") } @@ -703,24 +765,26 @@ func (p *Printer) command(cmd Command, redirs []*Redirect) (startRedirs int) { } p.WriteByte(')') p.wantSpace = true - sep := len(pl.Stmts) > 1 || (len(pl.Stmts) > 0 && pl.Stmts[0].Pos() > p.nline) - p.nestedStmts(pl.Stmts, 0) + sep := len(ci.Stmts) > 1 || (len(ci.Stmts) > 0 && ci.Stmts[0].Pos() > p.nline) + p.nestedStmts(ci.Stmts, 0) p.level++ if sep { - p.commentsUpTo(pl.OpPos) - p.newlines(pl.OpPos) + p.commentsUpTo(ci.OpPos) + p.newlines(ci.OpPos) } - p.spacedString(pl.Op.String()) - p.incLines(pl.OpPos) + p.spacedString(ci.Op.String()) + p.incLines(ci.OpPos) p.level-- - if sep || pl.OpPos == x.Esac { + if sep || ci.OpPos == x.Esac { p.wantNewline = true } } - p.decLevel() - p.semiRsrv("esac", x.Esac, len(x.List) == 0) + p.semiRsrv("esac", x.Esac, len(x.Items) == 0) case *ArithmCmd: p.WriteString("((") + if x.Unsigned { + p.WriteString("# ") + } p.arithmExpr(x.X, false, false) p.WriteString("))") case *TestClause: @@ -728,16 +792,17 @@ func (p *Printer) command(cmd Command, redirs []*Redirect) (startRedirs int) { p.testExpr(x.X) p.spacedString("]]") case *DeclClause: - name := x.Variant - if name == "" { - name = "declare" - } - p.spacedString(name) + p.spacedString(x.Variant) for _, w := range x.Opts { p.WriteByte(' ') p.word(w) } - p.assigns(x.Assigns) + p.assigns(x.Assigns, false) + case *TimeClause: + p.spacedString("time") + if x.Stmt != nil { + p.stmt(x.Stmt) + } case *CoprocClause: p.spacedString("coproc") if x.Name != nil { @@ -878,6 +943,7 @@ func (c *byteCounter) WriteString(s string) (int, error) { return 0, nil } func (c *byteCounter) Reset(io.Writer) { *c = 0 } +func (c *byteCounter) Flush() error { return nil } // stmtCols reports the length that s will take when formatted in a // single line. If it will span multiple lines, stmtCols will return -1. @@ -902,7 +968,7 @@ func (p *Printer) nestedStmts(stmts []*Stmt, closing Pos) { p.decLevel() } -func (p *Printer) assigns(assigns []*Assign) { +func (p *Printer) assigns(assigns []*Assign, alwaysEqual bool) { anyNewline := false for _, a := range assigns { if a.Pos() > p.nline { @@ -917,22 +983,20 @@ func (p *Printer) assigns(assigns []*Assign) { } if a.Name != nil { p.WriteString(a.Name.Value) - if a.Index != nil { - p.WriteByte('[') - p.arithmExpr(a.Index, false, false) - p.WriteByte(']') - } + p.wroteIndex(a.Index, a.Key) if a.Append { p.WriteByte('+') } - p.WriteByte('=') + if alwaysEqual || a.Value != nil || a.Array != nil { + p.WriteByte('=') + } } if a.Value != nil { p.word(a.Value) } else if a.Array != nil { p.wantSpace = false p.WriteByte('(') - p.wordJoin(a.Array.List, false) + p.elemJoin(a.Array.Elems) p.sepTok(")", a.Array.Rparen) } p.wantSpace = true diff --git a/vendor/github.com/mvdan/sh/syntax/token_string.go b/vendor/github.com/mvdan/sh/syntax/token_string.go index 4f37c872..a7481f40 100644 --- a/vendor/github.com/mvdan/sh/syntax/token_string.go +++ b/vendor/github.com/mvdan/sh/syntax/token_string.go @@ -1,12 +1,12 @@ -// Code generated by "stringer -type token"; DO NOT EDIT +// Code generated by "stringer -type token -linecoms"; DO NOT EDIT. package syntax import "fmt" -const _token_name = "illegalTokEOFLitLitWordLitRedir'\"`&&&||||&$$'$\"${$[$($(([(((}])));;;;&;;&!++--***==!=<=>=+=-=*=/=%=&=|=^=<<=>>=>>><<><&>&>|<<<<-<<<&>&>><(>(+:+-:-?:?=:=%%%###^^^,,,@///:-e-f-d-c-b-p-S-L-k-g-u-G-O-N-r-w-x-s-t-z-n-o-v-R=~-nt-ot-ef-eq-ne-le-ge-lt-gt?(*(+(@(!(" +const _token_name = "illegalTokEOFLitLitWordLitRedir'\"`&&&||||&$$'$\"${$[$($(([(((}])));;;;&;;&;|!++--***==!=<=>=+=-=*=/=%=&=|=^=<<=>>=>>><<><&>&>|<<<<-<<<&>&>><(>(+:+-:-?:?=:=%%%###^^^,,,@///:-e-f-d-c-b-p-S-L-k-g-u-G-O-N-r-w-x-s-t-z-n-o-v-R=~-nt-ot-ef-eq-ne-le-ge-lt-gt?(*(+(@(!(" -var _token_index = [...]uint16{0, 10, 13, 16, 23, 31, 32, 33, 34, 35, 37, 39, 40, 42, 43, 45, 47, 49, 51, 53, 56, 57, 58, 60, 61, 62, 63, 65, 66, 68, 70, 73, 74, 76, 78, 79, 81, 83, 85, 87, 89, 91, 93, 95, 97, 99, 101, 103, 105, 108, 111, 112, 114, 115, 117, 119, 121, 123, 125, 128, 131, 133, 136, 138, 140, 141, 143, 144, 146, 147, 149, 150, 152, 153, 155, 156, 158, 159, 161, 162, 164, 165, 166, 168, 169, 171, 173, 175, 177, 179, 181, 183, 185, 187, 189, 191, 193, 195, 197, 199, 201, 203, 205, 207, 209, 211, 213, 215, 217, 219, 222, 225, 228, 231, 234, 237, 240, 243, 246, 248, 250, 252, 254, 256} +var _token_index = [...]uint16{0, 10, 13, 16, 23, 31, 32, 33, 34, 35, 37, 39, 40, 42, 43, 45, 47, 49, 51, 53, 56, 57, 58, 60, 61, 62, 63, 65, 66, 68, 70, 73, 75, 76, 78, 80, 81, 83, 85, 87, 89, 91, 93, 95, 97, 99, 101, 103, 105, 107, 110, 113, 114, 116, 117, 119, 121, 123, 125, 127, 130, 133, 135, 138, 140, 142, 143, 145, 146, 148, 149, 151, 152, 154, 155, 157, 158, 160, 161, 163, 164, 166, 167, 168, 170, 171, 173, 175, 177, 179, 181, 183, 185, 187, 189, 191, 193, 195, 197, 199, 201, 203, 205, 207, 209, 211, 213, 215, 217, 219, 221, 224, 227, 230, 233, 236, 239, 242, 245, 248, 250, 252, 254, 256, 258} func (i token) String() string { if i >= token(len(_token_index)-1) { diff --git a/vendor/github.com/mvdan/sh/syntax/tokens.go b/vendor/github.com/mvdan/sh/syntax/tokens.go index 7bed2e02..e623135b 100644 --- a/vendor/github.com/mvdan/sh/syntax/tokens.go +++ b/vendor/github.com/mvdan/sh/syntax/tokens.go @@ -8,25 +8,26 @@ type token uint32 // Modified version of golang.org/x/tools/cmd/stringer that gets the // string value from the inline comment of each constant, if there is // one. Also removes leading '_'. -//go:generate stringer -type token +//go:generate stringer -type token -linecoms // The list of all possible tokens. const ( illegalTok token = iota - _EOF - _Lit - _LitWord - _LitRedir + + _EOF // EOF + _Lit // Lit + _LitWord // LitWord + _LitRedir // LitRedir sglQuote // ' dblQuote // " bckQuote // ` - and // & - andAnd // && - orOr // || - or // | - pipeAll // |& + and // & + andAnd // && + orOr // || + or // | + orAnd // |& dollar // $ dollSglQuote // $' @@ -46,8 +47,9 @@ const ( semicolon // ; dblSemicolon // ;; - semiFall // ;& - dblSemiFall // ;;& + semiAnd // ;& + dblSemiAnd // ;;& + semiOr // ;| exclMark // ! addAdd // ++ @@ -196,9 +198,10 @@ const ( type CaseOperator token const ( - DblSemicolon = CaseOperator(dblSemicolon) + iota - SemiFall - DblSemiFall + Break = CaseOperator(dblSemicolon) + iota + Fallthrough + Resume + ResumeKorn ) type ParExpOperator token diff --git a/vendor/github.com/mvdan/sh/syntax/walk.go b/vendor/github.com/mvdan/sh/syntax/walk.go index 87b4ce9a..8dbfb61c 100644 --- a/vendor/github.com/mvdan/sh/syntax/walk.go +++ b/vendor/github.com/mvdan/sh/syntax/walk.go @@ -79,7 +79,7 @@ func Walk(node Node, f func(Node) bool) { walkStmts(x.DoStmts, f) case *WordIter: Walk(x.Name, f) - walkWords(x.List, f) + walkWords(x.Items, f) case *CStyleLoop: if x.Init != nil { Walk(x.Init, f) @@ -116,20 +116,20 @@ func Walk(node Node, f func(Node) bool) { Walk(x.Index, f) } if x.Repl != nil { - Walk(x.Repl.Orig, f) - Walk(x.Repl.With, f) + if x.Repl.Orig != nil { + Walk(x.Repl.Orig, f) + } + if x.Repl.With != nil { + Walk(x.Repl.With, f) + } } - if x.Exp != nil { + if x.Exp != nil && x.Exp.Word != nil { Walk(x.Exp.Word, f) } case *ArithmExp: - if x.X != nil { - Walk(x.X, f) - } + Walk(x.X, f) case *ArithmCmd: - if x.X != nil { - Walk(x.X, f) - } + Walk(x.X, f) case *BinaryArithm: Walk(x.X, f) Walk(x.Y, f) @@ -146,9 +146,9 @@ func Walk(node Node, f func(Node) bool) { Walk(x.X, f) case *CaseClause: Walk(x.Word, f) - for _, pl := range x.List { - walkWords(pl.Patterns, f) - walkStmts(pl.Stmts, f) + for _, ci := range x.Items { + walkWords(ci.Patterns, f) + walkStmts(ci.Stmts, f) } case *TestClause: Walk(x.X, f) @@ -158,11 +158,22 @@ func Walk(node Node, f func(Node) bool) { Walk(a, f) } case *ArrayExpr: - walkWords(x.List, f) + for _, el := range x.Elems { + Walk(el, f) + } + case *ArrayElem: + if x.Index != nil { + Walk(x.Index, f) + } + Walk(x.Value, f) case *ExtGlob: Walk(x.Pattern, f) case *ProcSubst: walkStmts(x.Stmts, f) + case *TimeClause: + if x.Stmt != nil { + Walk(x.Stmt, f) + } case *CoprocClause: if x.Name != nil { Walk(x.Name, f) diff --git a/vendor/vendor.json b/vendor/vendor.json index ca5657c3..4e0c95de 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -51,16 +51,16 @@ "revisionTime": "2017-01-24T11:57:57Z" }, { - "checksumSHA1": "ohm6oyTSFu/xZk5HtTUG7RIONO4=", + "checksumSHA1": "dZOPdDhOrEZfccmKxqRLsbCwm6g=", "path": "github.com/mvdan/sh/interp", - "revision": "380eaf2df0412887a2240f5b15e76ac810ca2e71", - "revisionTime": "2017-05-17T16:44:15Z" + "revision": "27fa62d749f4f87716626413e5960586c91e36f9", + "revisionTime": "2017-05-26T14:37:19Z" }, { - "checksumSHA1": "2OcfNJuStj/eAcEPW5yRdU02DCc=", + "checksumSHA1": "vGslbhw/1KheACSg3CayaaKY6DA=", "path": "github.com/mvdan/sh/syntax", - "revision": "380eaf2df0412887a2240f5b15e76ac810ca2e71", - "revisionTime": "2017-05-17T16:44:15Z" + "revision": "27fa62d749f4f87716626413e5960586c91e36f9", + "revisionTime": "2017-05-26T14:37:19Z" }, { "checksumSHA1": "HUXE+Nrcau8FSaVEvPYHMvDjxOE=",