Update dependencies

This commit is contained in:
Andrey Nering
2017-06-04 16:06:04 -03:00
parent f98bf6c4b1
commit 09e6d5269d
130 changed files with 26814 additions and 19205 deletions

View File

@@ -19,7 +19,7 @@ func isBuiltin(name string) bool {
"echo", "printf", "break", "continue", "pwd", "cd",
"wait", "builtin", "trap", "type", "source", "command",
"pushd", "popd", "umask", "alias", "unalias", "fg", "bg",
"getopts", "eval":
"getopts", "eval", "test", "[":
return true
}
return false
@@ -207,6 +207,23 @@ func (r *Runner) builtinCode(pos syntax.Pos, name string, args []string) int {
r2.File = file
r2.Run()
return r2.exit
case "[":
if len(args) == 0 || args[len(args)-1] != "]" {
r.runErr(pos, "[: missing matching ]")
break
}
args = args[:len(args)-1]
fallthrough
case "test":
p := testParser{
rem: args,
err: func(format string, a ...interface{}) {
r.runErr(pos, format, a...)
},
}
p.next()
expr := p.classicTest("[", false)
return oneIf(r.bashTest(expr) == "")
case "trap", "source", "command", "pushd", "popd",
"umask", "alias", "unalias", "fg", "bg", "getopts":
r.runErr(pos, "unhandled builtin: %s", name)

View File

@@ -563,6 +563,7 @@ func (r *Runner) loopStmtsBroken(stmts []*syntax.Stmt) bool {
func (r *Runner) wordParts(wps []syntax.WordPart, quoted bool) []string {
var parts []string
var curBuf bytes.Buffer
allowEmpty := false
flush := func() {
if curBuf.Len() == 0 {
return
@@ -590,17 +591,18 @@ func (r *Runner) wordParts(wps []syntax.WordPart, quoted bool) []string {
}
curBuf.WriteString(s)
case *syntax.SglQuoted:
allowEmpty = true
curBuf.WriteString(x.Value)
case *syntax.DblQuoted:
// TODO: @ between double quotes but not alone
allowEmpty = true
if len(x.Parts) == 1 {
pe, ok := x.Parts[0].(*syntax.ParamExp)
if ok && pe.Param.Value == "@" {
for i, arg := range r.args {
pe, _ := x.Parts[0].(*syntax.ParamExp)
if elems := r.quotedElems(pe); elems != nil {
for i, elem := range elems {
if i > 0 {
flush()
}
curBuf.WriteString(arg)
curBuf.WriteString(elem)
}
continue
}
@@ -617,10 +619,10 @@ func (r *Runner) wordParts(wps []syntax.WordPart, quoted bool) []string {
}
case *syntax.CmdSubst:
r2 := *r
var outBuf bytes.Buffer
r2.Stdout = &outBuf
var buf bytes.Buffer
r2.Stdout = &buf
r2.stmts(x.Stmts)
val := strings.TrimRight(outBuf.String(), "\n")
val := strings.TrimRight(buf.String(), "\n")
if quoted {
curBuf.WriteString(val)
} else {
@@ -633,6 +635,9 @@ func (r *Runner) wordParts(wps []syntax.WordPart, quoted bool) []string {
}
}
flush()
if allowEmpty && len(parts) == 0 {
parts = append(parts, "")
}
return parts
}

View File

@@ -12,6 +12,29 @@ import (
"github.com/mvdan/sh/syntax"
)
func (r *Runner) quotedElems(pe *syntax.ParamExp) []string {
if pe == nil {
return nil
}
if pe.Param.Value == "@" {
return r.args
}
w, _ := pe.Index.(*syntax.Word)
if w == nil || len(w.Parts) != 1 {
return nil
}
l, _ := w.Parts[0].(*syntax.Lit)
if l == nil || l.Value != "@" {
return nil
}
val, _ := r.lookupVar(pe.Param.Value)
switch x := val.(type) {
case []string:
return x
}
return nil
}
func (r *Runner) paramExp(pe *syntax.ParamExp) string {
name := pe.Param.Value
var val varValue

182
vendor/github.com/mvdan/sh/interp/test_classic.go generated vendored Normal file
View File

@@ -0,0 +1,182 @@
// Copyright (c) 2017, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
package interp
import (
"github.com/mvdan/sh/syntax"
)
const illegalTok = 0
type testParser struct {
eof bool
val string
rem []string
err func(format string, a ...interface{})
}
func (p *testParser) next() {
if p.eof || len(p.rem) == 0 {
p.eof = true
return
}
p.val = p.rem[0]
p.rem = p.rem[1:]
}
func (p *testParser) followWord(fval string) *syntax.Word {
if p.eof {
p.err("%s must be followed by a word", fval)
}
w := &syntax.Word{Parts: []syntax.WordPart{
&syntax.Lit{Value: p.val},
}}
p.next()
return w
}
func (p *testParser) classicTest(fval string, pastAndOr bool) syntax.TestExpr {
var left syntax.TestExpr
if pastAndOr {
left = p.testExprBase(fval)
} else {
left = p.classicTest(fval, true)
}
if left == nil || p.eof {
return left
}
opStr := p.val
op := testBinaryOp(p.val)
if op == illegalTok {
p.err("not a valid test operator: %s", p.val)
}
b := &syntax.BinaryTest{
Op: op,
X: left,
}
p.next()
switch b.Op {
case syntax.AndTest, syntax.OrTest:
if b.Y = p.classicTest(opStr, false); b.Y == nil {
p.err("%s must be followed by an expression", opStr)
}
default:
b.Y = p.followWord(opStr)
}
return b
}
func (p *testParser) testExprBase(fval string) syntax.TestExpr {
if p.eof {
return nil
}
op := testUnaryOp(p.val)
switch op {
case syntax.TsNot:
u := &syntax.UnaryTest{Op: op}
p.next()
u.X = p.classicTest(op.String(), false)
return u
case illegalTok:
return p.followWord(fval)
default:
u := &syntax.UnaryTest{Op: op}
p.next()
u.X = p.followWord(op.String())
return u
}
}
// testUnaryOp is an exact copy of syntax's.
func testUnaryOp(val string) syntax.UnTestOperator {
switch val {
case "!":
return syntax.TsNot
case "-e", "-a":
return syntax.TsExists
case "-f":
return syntax.TsRegFile
case "-d":
return syntax.TsDirect
case "-c":
return syntax.TsCharSp
case "-b":
return syntax.TsBlckSp
case "-p":
return syntax.TsNmPipe
case "-S":
return syntax.TsSocket
case "-L", "-h":
return syntax.TsSmbLink
case "-k":
return syntax.TsSticky
case "-g":
return syntax.TsGIDSet
case "-u":
return syntax.TsUIDSet
case "-G":
return syntax.TsGrpOwn
case "-O":
return syntax.TsUsrOwn
case "-N":
return syntax.TsModif
case "-r":
return syntax.TsRead
case "-w":
return syntax.TsWrite
case "-x":
return syntax.TsExec
case "-s":
return syntax.TsNoEmpty
case "-t":
return syntax.TsFdTerm
case "-z":
return syntax.TsEmpStr
case "-n":
return syntax.TsNempStr
case "-o":
return syntax.TsOptSet
case "-v":
return syntax.TsVarSet
case "-R":
return syntax.TsRefVar
default:
return illegalTok
}
}
// testBinaryOp is like syntax's, but with -a and -o, and without =~.
func testBinaryOp(val string) syntax.BinTestOperator {
switch val {
case "-a":
return syntax.AndTest
case "-o":
return syntax.OrTest
case "==", "=":
return syntax.TsMatch
case "!=":
return syntax.TsNoMatch
case "-nt":
return syntax.TsNewer
case "-ot":
return syntax.TsOlder
case "-ef":
return syntax.TsDevIno
case "-eq":
return syntax.TsEql
case "-ne":
return syntax.TsNeq
case "-le":
return syntax.TsLeq
case "-ge":
return syntax.TsGeq
case "-lt":
return syntax.TsLss
case "-gt":
return syntax.TsGtr
default:
return illegalTok
}
}

View File

@@ -28,6 +28,15 @@ func paramOps(r rune) bool {
return false
}
// these start a parameter expansion name
func paramNameOp(r rune) bool {
switch r {
case '}', ':', '+', '=', '%', '[', ']', '/', '^', ',', '@':
return false
}
return true
}
// tokenize these inside arithmetic expansions
func arithmOps(r rune) bool {
switch r {
@@ -86,14 +95,6 @@ retry:
return p.r
}
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
}
}
// fill reads more bytes from the input src into readBuf. Any bytes that
// had not yet been used at the end of the buffer are slid into the
// beginning of the buffer.
@@ -528,7 +529,7 @@ func (p *Parser) paramToken(r rune) token {
p.rune()
return leftBrack
case '/':
if p.rune() == '/' {
if p.rune() == '/' && p.quote != paramExpRepl {
p.rune()
return dblSlash
}
@@ -961,90 +962,90 @@ loop:
p.tok, p.val = _LitWord, p.endLit()
}
func testUnaryOp(val string) token {
func testUnaryOp(val string) UnTestOperator {
switch val {
case "!":
return exclMark
return TsNot
case "-e", "-a":
return tsExists
return TsExists
case "-f":
return tsRegFile
return TsRegFile
case "-d":
return tsDirect
return TsDirect
case "-c":
return tsCharSp
return TsCharSp
case "-b":
return tsBlckSp
return TsBlckSp
case "-p":
return tsNmPipe
return TsNmPipe
case "-S":
return tsSocket
return TsSocket
case "-L", "-h":
return tsSmbLink
return TsSmbLink
case "-k":
return tsSticky
return TsSticky
case "-g":
return tsGIDSet
return TsGIDSet
case "-u":
return tsUIDSet
return TsUIDSet
case "-G":
return tsGrpOwn
return TsGrpOwn
case "-O":
return tsUsrOwn
return TsUsrOwn
case "-N":
return tsModif
return TsModif
case "-r":
return tsRead
return TsRead
case "-w":
return tsWrite
return TsWrite
case "-x":
return tsExec
return TsExec
case "-s":
return tsNoEmpty
return TsNoEmpty
case "-t":
return tsFdTerm
return TsFdTerm
case "-z":
return tsEmpStr
return TsEmpStr
case "-n":
return tsNempStr
return TsNempStr
case "-o":
return tsOptSet
return TsOptSet
case "-v":
return tsVarSet
return TsVarSet
case "-R":
return tsRefVar
return TsRefVar
default:
return illegalTok
return 0
}
}
func testBinaryOp(val string) token {
func testBinaryOp(val string) BinTestOperator {
switch val {
case "==", "=":
return equal
return TsMatch
case "!=":
return nequal
return TsNoMatch
case "=~":
return tsReMatch
return TsReMatch
case "-nt":
return tsNewer
return TsNewer
case "-ot":
return tsOlder
return TsOlder
case "-ef":
return tsDevIno
return TsDevIno
case "-eq":
return tsEql
return TsEql
case "-ne":
return tsNeq
return TsNeq
case "-le":
return tsLeq
return TsLeq
case "-ge":
return tsGeq
return TsGeq
case "-lt":
return tsLss
return TsLss
case "-gt":
return tsGtr
return TsGtr
default:
return illegalTok
return 0
}
}

View File

@@ -111,17 +111,16 @@ 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
Negated bool // ! stmt
Background bool // stmt &
Coprocess bool // mksh's |&
Assigns []*Assign // a=x b=y stmt
Redirs []*Redirect // stmt >a <b
}
func (s *Stmt) Pos() Pos { return s.Position }
@@ -174,13 +173,13 @@ func (*CoprocClause) commandNode() {}
// Assign represents an assignment to a variable.
type Assign struct {
Append bool
Naked bool
Append bool // +=
Naked bool // without '='
Name *Lit
Index ArithmExpr
Key *DblQuoted
Value *Word
Array *ArrayExpr
Index ArithmExpr // [i]
Key *DblQuoted // ["k"]
Value *Word // =val
Array *ArrayExpr // =(arr)
}
func (a *Assign) Pos() Pos { return a.Name.Pos() }
@@ -193,8 +192,7 @@ func (a *Assign) End() Pos {
}
if a.Index != nil {
return a.Index.End() + 2
}
if a.Key != nil {
} else if a.Key != nil {
return a.Key.End() + 2
}
if a.Naked {
@@ -205,10 +203,11 @@ func (a *Assign) End() Pos {
// Redirect represents an input/output redirection.
type Redirect struct {
OpPos Pos
Op RedirOperator
N *Lit
Word, Hdoc *Word
OpPos Pos
Op RedirOperator
N *Lit // N>
Word *Word // >word
Hdoc *Word // here-document body
}
func (r *Redirect) Pos() Pos {
@@ -330,10 +329,10 @@ func (b *BinaryCmd) End() Pos { return b.Y.End() }
// FuncDecl represents the declaration of a function.
type FuncDecl struct {
Position Pos
BashStyle bool
Name *Lit
Body *Stmt
Position Pos
RsrvWord bool // non-posix "function " style
Name *Lit
Body *Stmt
}
func (f *FuncDecl) Pos() Pos { return f.Position }
@@ -379,7 +378,7 @@ func (l *Lit) End() Pos { return l.ValueEnd }
// SglQuoted represents a string within single quotes.
type SglQuoted struct {
Position Pos
Dollar bool
Dollar bool // $''
Value string
}
@@ -395,7 +394,7 @@ func (q *SglQuoted) End() Pos {
// DblQuoted represents a list of nodes within double quotes.
type DblQuoted struct {
Position Pos
Dollar bool
Dollar bool // $""
Parts []WordPart
}
@@ -415,10 +414,8 @@ 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
TempFile bool // mksh's ${ foo;}
ReplyVar bool // mksh's ${|foo;}
}
func (c *CmdSubst) Pos() Pos { return c.Left }
@@ -427,16 +424,16 @@ func (c *CmdSubst) End() Pos { return c.Right + 1 }
// ParamExp represents a parameter expansion.
type ParamExp struct {
Dollar, Rbrace Pos
Short bool
Indirect bool
Length bool
Width bool
Short bool // $a instead of ${a}
Indirect bool // ${!a}
Length bool // ${#a}
Width bool // ${%a}
Param *Lit
Index ArithmExpr
Key *DblQuoted
Slice *Slice
Repl *Replace
Exp *Expansion
Index ArithmExpr // ${a[i]}
Key *DblQuoted // ${a["k"]}
Slice *Slice // ${a:x:y}
Repl *Replace // ${a/x/y}
Exp *Expansion // ${a:-b}, ${a#b}, etc
}
func (p *ParamExp) Pos() Pos { return p.Dollar }
@@ -446,8 +443,7 @@ func (p *ParamExp) End() Pos {
}
if p.Index != nil {
return p.Index.End() + 1
}
if p.Key != nil {
} else if p.Key != nil {
return p.Key.End() + 1
}
return p.Param.End()
@@ -480,8 +476,8 @@ type Expansion struct {
// ArithmExp represents an arithmetic expansion.
type ArithmExp struct {
Left, Right Pos
Bracket bool
Unsigned bool
Bracket bool // deprecated $[expr] form
Unsigned bool // mksh's $((# expr))
X ArithmExpr
}
@@ -498,7 +494,7 @@ func (a *ArithmExp) End() Pos {
// This node will never appear when in PosixConformant mode.
type ArithmCmd struct {
Left, Right Pos
Unsigned bool
Unsigned bool // mksh's ((# expr))
X ArithmExpr
}
@@ -651,7 +647,7 @@ func (p *ParenTest) End() Pos { return p.Rparen + 1 }
// This node will never appear when in PosixConformant mode.
type DeclClause struct {
Position Pos
Variant string
Variant string // "declare", "local", etc
Opts []*Word
Assigns []*Assign
}
@@ -684,8 +680,7 @@ type ArrayElem struct {
func (a *ArrayElem) Pos() Pos {
if a.Index != nil {
return a.Index.Pos()
}
if a.Key != nil {
} else if a.Key != nil {
return a.Key.Pos()
}
return a.Value.Pos()

View File

@@ -118,7 +118,7 @@ func (p *Parser) getPos() Pos { return Pos(p.offs + p.npos) }
func (p *Parser) lit(pos Pos, val string) *Lit {
if len(p.litBatch) == 0 {
p.litBatch = make([]Lit, 64)
p.litBatch = make([]Lit, 128)
}
l := &p.litBatch[0]
p.litBatch = p.litBatch[1:]
@@ -130,7 +130,7 @@ func (p *Parser) lit(pos Pos, val string) *Lit {
func (p *Parser) word(parts []WordPart) *Word {
if len(p.wordBatch) == 0 {
p.wordBatch = make([]Word, 32)
p.wordBatch = make([]Word, 64)
}
w := &p.wordBatch[0]
p.wordBatch = p.wordBatch[1:]
@@ -150,7 +150,7 @@ func (p *Parser) wps(wp WordPart) []WordPart {
func (p *Parser) stmt(pos Pos) *Stmt {
if len(p.stmtBatch) == 0 {
p.stmtBatch = make([]Stmt, 16)
p.stmtBatch = make([]Stmt, 64)
}
s := &p.stmtBatch[0]
p.stmtBatch = p.stmtBatch[1:]
@@ -160,7 +160,7 @@ func (p *Parser) stmt(pos Pos) *Stmt {
func (p *Parser) stList() []*Stmt {
if len(p.stListBatch) == 0 {
p.stListBatch = make([]*Stmt, 128)
p.stListBatch = make([]*Stmt, 256)
}
stmts := p.stListBatch[:0:4]
p.stListBatch = p.stListBatch[4:]
@@ -184,7 +184,7 @@ func (p *Parser) call(w *Word) *CallExpr {
return ce
}
type quoteState uint
type quoteState uint32
const (
noState quoteState = 1 << iota
@@ -557,9 +557,9 @@ func (p *Parser) wordPart() WordPart {
p.curErr(`"${ stmts;}" is a mksh feature`)
}
cs := &CmdSubst{
Left: p.pos,
MirBSDTempFile: p.r != '|',
MirBSDReplyVar: p.r == '|',
Left: p.pos,
TempFile: p.r != '|',
ReplyVar: p.r == '|',
}
old := p.preNested(subCmd)
p.rune() // don't tokenize '|'
@@ -792,6 +792,9 @@ func (p *Parser) arithmExpr(ftok token, fpos Pos, level int, compact, tern bool)
case _Lit, _LitWord:
p.curErr("not a valid arithmetic operator: %s", p.val)
return nil
case leftBrack:
p.curErr("[ must follow a name")
return nil
case rightParen, _EOF:
default:
if p.quote == arithmExpr {
@@ -845,7 +848,7 @@ func isArithName(left ArithmExpr) bool {
}
switch x := w.Parts[0].(type) {
case *Lit:
return validIdent(x.Value)
return ValidName(x.Value)
case *ParamExp:
return x.nakedIndex()
default:
@@ -905,10 +908,6 @@ func (p *Parser) arithmExprBase(compact bool) ArithmExpr {
p.postNested(old)
p.matched(left, leftBrack, rightBrack)
x = p.word(p.wps(pe))
case dollar:
x = p.word(p.wps(p.shortParamExp()))
case dollBrace:
x = p.word(p.wps(p.paramExp()))
case bckQuote:
if p.quote == arithmExprLet {
return nil
@@ -964,15 +963,18 @@ func (p *Parser) paramExp() *ParamExp {
pe := &ParamExp{Dollar: p.pos}
old := p.quote
p.quote = paramExpName
p.next()
if p.r == '#' {
p.tok = hash
p.pos = p.getPos()
p.rune()
} else {
p.next()
}
switch p.tok {
case at:
p.tok, p.val = _LitWord, "@"
case dblHash:
p.unrune('#', hash)
fallthrough
case hash:
if p.r != '}' {
if paramNameOp(p.r) {
pe.Length = true
p.next()
}
@@ -980,12 +982,12 @@ func (p *Parser) paramExp() *ParamExp {
if p.lang != LangMirBSDKorn {
p.posErr(pe.Pos(), `"${%%foo}" is a mksh feature`)
}
if p.r != '}' {
if paramNameOp(p.r) {
pe.Width = true
p.next()
}
case exclMark:
if p.r != '}' {
if paramNameOp(p.r) {
pe.Indirect = true
p.next()
}
@@ -1006,9 +1008,7 @@ func (p *Parser) paramExp() *ParamExp {
p.curErr("%s cannot be followed by a word", op)
}
default:
if !pe.Length {
p.posErr(pe.Dollar, "parameter expansion requires a literal")
}
p.curErr("parameter expansion requires a literal")
}
if p.tok == rightBrace {
pe.Rbrace = p.pos
@@ -1050,14 +1050,9 @@ func (p *Parser) paramExp() *ParamExp {
p.next()
pe.Repl.Orig = p.getWord()
p.quote = paramExpExp
switch p.tok {
case dblSlash:
p.unrune('/', slash)
p.next()
case slash:
p.next()
if p.got(slash) {
pe.Repl.With = p.getWord()
}
pe.Repl.With = p.getWord()
case colon:
if p.lang == LangPOSIX {
p.curErr("slicing is a bash feature")
@@ -1119,7 +1114,8 @@ func stopToken(tok token) bool {
return false
}
func validIdent(val string) bool {
// ValidName returns whether val is a valid name as per the POSIX spec.
func ValidName(val string) bool {
for i, c := range val {
switch {
case 'a' <= c && c <= 'z':
@@ -1138,7 +1134,7 @@ func (p *Parser) hasValidIdent() bool {
if p.val[end-1] == '+' && p.lang != LangPOSIX {
end--
}
if validIdent(p.val[:end]) {
if ValidName(p.val[:end]) {
return true
}
}
@@ -1387,57 +1383,51 @@ preLoop:
case "esac":
p.curErr(`%q can only be used to end a case`, p.val)
case "[[":
if p.lang == LangPOSIX {
break
if p.lang != LangPOSIX {
s.Cmd = p.testClause()
}
s.Cmd = p.testClause()
case "]]":
if p.lang == LangPOSIX {
break
if p.lang != LangPOSIX {
p.curErr(`%s can only be used to close a test`,
p.val)
}
p.curErr(`%s can only be used to close a test`, p.val)
case "let":
if p.lang == LangPOSIX {
break
if p.lang != LangPOSIX {
s.Cmd = p.letClause()
}
s.Cmd = p.letClause()
case "function":
if p.lang == LangPOSIX {
break
if p.lang != LangPOSIX {
s.Cmd = p.bashFuncDecl()
}
s.Cmd = p.bashFuncDecl()
case "declare":
if p.lang != LangBash {
break
if p.lang == LangBash {
s.Cmd = p.declClause()
}
s.Cmd = p.declClause()
case "local", "export", "readonly", "typeset", "nameref":
if p.lang == LangPOSIX {
break
if p.lang != LangPOSIX {
s.Cmd = p.declClause()
}
s.Cmd = p.declClause()
case "time":
if p.lang == LangPOSIX {
break
if p.lang != LangPOSIX {
s.Cmd = p.timeClause()
}
s.Cmd = p.timeClause()
case "coproc":
if p.lang != LangBash {
break
if p.lang == LangBash {
s.Cmd = p.coprocClause()
}
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)))
if s.Cmd != nil {
break
}
name := p.lit(p.pos, p.val)
if p.next(); p.gotSameLine(leftParen) {
p.follow(name.ValuePos, "foo(", rightParen)
if p.lang == LangPOSIX && !ValidName(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)))
}
case bckQuote:
if p.quote == subCmdBckquo {
@@ -1576,25 +1566,16 @@ func (p *Parser) loop(forPos Pos) Loop {
if p.tok == dblLeftParen {
cl := &CStyleLoop{Lparen: p.pos}
old := p.preNested(arithmExprCmd)
if p.next(); p.tok == dblSemicolon {
p.unrune(';', semicolon)
}
if p.tok != semicolon {
cl.Init = p.arithmExpr(dblLeftParen, cl.Lparen, 0, false, false)
}
p.next()
cl.Init = p.arithmExpr(dblLeftParen, cl.Lparen, 0, false, false)
scPos := p.pos
if p.tok == dblSemicolon {
p.unrune(';', semicolon)
}
p.follow(p.pos, "expression", semicolon)
if p.tok != semicolon {
if !p.got(dblSemicolon) {
p.follow(p.pos, "expr", semicolon)
cl.Cond = p.arithmExpr(semicolon, scPos, 0, false, false)
scPos = p.pos
p.follow(p.pos, "expr", semicolon)
}
scPos = p.pos
p.follow(p.pos, "expression", semicolon)
if p.tok != semicolon {
cl.Post = p.arithmExpr(semicolon, scPos, 0, false, false)
}
cl.Post = p.arithmExpr(semicolon, scPos, 0, false, false)
cl.Rparen = p.arithmEnd(dblLeftParen, cl.Lparen, old)
p.gotSameLine(semicolon)
return cl
@@ -1677,7 +1658,7 @@ func (p *Parser) testClause() *TestClause {
if p.next(); p.tok == _EOF || p.gotRsrv("]]") {
p.posErr(tc.Left, "test clause requires at least one expression")
}
tc.X = p.testExpr(illegalTok, tc.Left, 0)
tc.X = p.testExpr(illegalTok, tc.Left, false)
tc.Right = p.pos
if !p.gotRsrv("]]") {
p.matchingErr(tc.Left, "[[", "]]")
@@ -1685,26 +1666,23 @@ func (p *Parser) testClause() *TestClause {
return tc
}
func (p *Parser) testExpr(ftok token, fpos Pos, level int) TestExpr {
func (p *Parser) testExpr(ftok token, fpos Pos, pastAndOr bool) TestExpr {
var left TestExpr
if level > 1 {
if pastAndOr {
left = p.testExprBase(ftok, fpos)
} else {
left = p.testExpr(ftok, fpos, level+1)
left = p.testExpr(ftok, fpos, true)
}
if left == nil {
return left
}
var newLevel int
switch p.tok {
case andAnd, orOr:
case _LitWord:
if p.val == "]]" {
return left
}
fallthrough
case rdrIn, rdrOut:
newLevel = 1
case _EOF, rightParen:
return left
case _Lit:
@@ -1712,11 +1690,8 @@ func (p *Parser) testExpr(ftok token, fpos Pos, level int) TestExpr {
default:
p.curErr("not a valid test operator: %v", p.tok)
}
if newLevel < level {
return left
}
if p.tok == _LitWord {
if p.tok = testBinaryOp(p.val); p.tok == illegalTok {
if p.tok = token(testBinaryOp(p.val)); p.tok == illegalTok {
p.curErr("not a valid test operator: %s", p.val)
}
}
@@ -1728,7 +1703,7 @@ func (p *Parser) testExpr(ftok token, fpos Pos, level int) TestExpr {
switch b.Op {
case AndTest, OrTest:
p.next()
if b.Y = p.testExpr(token(b.Op), b.OpPos, newLevel); b.Y == nil {
if b.Y = p.testExpr(token(b.Op), b.OpPos, false); b.Y == nil {
p.followErrExp(b.OpPos, b.Op.String())
}
case TsReMatch:
@@ -1754,11 +1729,10 @@ func (p *Parser) testExprBase(ftok token, fpos Pos) TestExpr {
case _EOF:
return nil
case _LitWord:
op := testUnaryOp(p.val)
op := token(testUnaryOp(p.val))
switch op {
case illegalTok:
case tsRefVar, tsModif:
// TODO: check with man mksh
case tsRefVar, tsModif: // not available in mksh
if p.lang == LangBash {
p.tok = op
}
@@ -1770,7 +1744,7 @@ 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, 0)
u.X = p.testExpr(token(u.Op), u.OpPos, false)
return u
case tsExists, tsRegFile, tsDirect, tsCharSp, tsBlckSp, tsNmPipe,
tsSocket, tsSmbLink, tsSticky, tsGIDSet, tsUIDSet, tsGrpOwn,
@@ -1783,7 +1757,7 @@ func (p *Parser) testExprBase(ftok token, fpos Pos) TestExpr {
case leftParen:
pe := &ParenTest{Lparen: p.pos}
p.next()
if pe.X = p.testExpr(leftParen, pe.Lparen, 0); pe.X == nil {
if pe.X = p.testExpr(leftParen, pe.Lparen, false); pe.X == nil {
p.followErrExp(pe.Lparen, "(")
}
pe.Rparen = p.matched(pe.Lparen, leftParen, rightParen)
@@ -1953,9 +1927,9 @@ func (p *Parser) callExpr(s *Stmt, w *Word) *CallExpr {
func (p *Parser) funcDecl(name *Lit, pos Pos) *FuncDecl {
fd := &FuncDecl{
Position: pos,
BashStyle: pos != name.ValuePos,
Name: name,
Position: pos,
RsrvWord: pos != name.ValuePos,
Name: name,
}
if fd.Body, _ = p.getStmt(false, false); fd.Body == nil {
p.followErr(fd.Pos(), "foo()", "a statement")

View File

@@ -289,13 +289,13 @@ func (p *Printer) wordPart(wp WordPart) {
case *CmdSubst:
p.incLines(x.Pos())
switch {
case x.MirBSDTempFile:
case x.TempFile:
p.WriteString("${")
p.wantSpace = true
p.nestedStmts(x.Stmts, x.Right)
p.wantSpace = false
p.semiRsrv("}", x.Right, true)
case x.MirBSDReplyVar:
case x.ReplyVar:
p.WriteString("${|")
p.nestedStmts(x.Stmts, x.Right)
p.wantSpace = false
@@ -380,9 +380,7 @@ func (p *Printer) paramExp(pe *ParamExp) {
case pe.Indirect:
p.WriteByte('!')
}
if pe.Param != nil {
p.WriteString(pe.Param.Value)
}
p.WriteString(pe.Param.Value)
p.wroteIndex(pe.Index, pe.Key)
if pe.Slice != nil {
p.WriteByte(':')
@@ -741,7 +739,7 @@ func (p *Printer) command(cmd Command, redirs []*Redirect) (startRedirs int) {
}
p.nestedBinary = false
case *FuncDecl:
if x.BashStyle {
if x.RsrvWord {
p.WriteString("function ")
}
p.WriteString(x.Name.Value)

243
vendor/github.com/mvdan/sh/syntax/simplify.go generated vendored Normal file
View File

@@ -0,0 +1,243 @@
// Copyright (c) 2017, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
package syntax
import "bytes"
// Simplify simplifies a given program and returns whether any changes
// were made.
//
// This function is EXPERIMENTAL; it may change or disappear at any
// point until this notice is removed.
func Simplify(f *File) bool {
s := simplifier{}
Walk(f, s.visit)
return s.modified
}
type simplifier struct {
modified bool
}
func (s *simplifier) visit(node Node) bool {
switch x := node.(type) {
case *Assign:
if x.Index != nil {
x.Index = s.removeParensArithm(x.Index)
x.Index = s.inlineSimpleParams(x.Index)
}
case *ParamExp:
if x.Index != nil {
x.Index = s.removeParensArithm(x.Index)
x.Index = s.inlineSimpleParams(x.Index)
}
if x.Slice == nil {
break
}
if x.Slice.Offset != nil {
x.Slice.Offset = s.removeParensArithm(x.Slice.Offset)
x.Slice.Offset = s.inlineSimpleParams(x.Slice.Offset)
}
if x.Slice.Length != nil {
x.Slice.Length = s.removeParensArithm(x.Slice.Length)
x.Slice.Length = s.inlineSimpleParams(x.Slice.Length)
}
case *ArithmExp:
x.X = s.removeParensArithm(x.X)
x.X = s.inlineSimpleParams(x.X)
case *ArithmCmd:
x.X = s.removeParensArithm(x.X)
x.X = s.inlineSimpleParams(x.X)
case *ParenArithm:
x.X = s.removeParensArithm(x.X)
x.X = s.inlineSimpleParams(x.X)
case *BinaryArithm:
x.X = s.inlineSimpleParams(x.X)
x.Y = s.inlineSimpleParams(x.Y)
case *CmdSubst:
x.Stmts = s.inlineSubshell(x.Stmts)
case *Subshell:
x.Stmts = s.inlineSubshell(x.Stmts)
case *Word:
x.Parts = s.simplifyWord(x.Parts)
case *TestClause:
x.X = s.removeParensTest(x.X)
x.X = s.removeNegateTest(x.X)
case *ParenTest:
x.X = s.removeParensTest(x.X)
x.X = s.removeNegateTest(x.X)
case *BinaryTest:
x.X = s.unquoteParams(x.X)
x.X = s.removeNegateTest(x.X)
switch x.Op {
case TsMatch, TsNoMatch:
// unquoting enables globbing
default:
x.Y = s.unquoteParams(x.Y)
}
x.Y = s.removeNegateTest(x.Y)
case *UnaryTest:
x.X = s.unquoteParams(x.X)
}
return true
}
func (s *simplifier) simplifyWord(wps []WordPart) []WordPart {
parts:
for i, wp := range wps {
dq, _ := wp.(*DblQuoted)
if dq == nil || len(dq.Parts) != 1 {
break
}
lit, _ := dq.Parts[0].(*Lit)
if lit == nil {
break
}
var buf bytes.Buffer
escaped := false
for _, r := range lit.Value {
switch r {
case '\\':
escaped = !escaped
if escaped {
continue
}
case '\'':
continue parts
case '$', '"', '`':
escaped = false
default:
if escaped {
continue parts
}
escaped = false
}
buf.WriteRune(r)
}
newVal := buf.String()
if newVal == lit.Value {
break
}
s.modified = true
wps[i] = &SglQuoted{
Position: dq.Position,
Dollar: dq.Dollar,
Value: newVal,
}
}
return wps
}
func (s *simplifier) removeParensArithm(x ArithmExpr) ArithmExpr {
for {
par, _ := x.(*ParenArithm)
if par == nil {
return x
}
s.modified = true
x = par.X
}
}
func (s *simplifier) inlineSimpleParams(x ArithmExpr) ArithmExpr {
w, _ := x.(*Word)
if w == nil || len(w.Parts) != 1 {
return x
}
pe, _ := w.Parts[0].(*ParamExp)
if pe == nil || !ValidName(pe.Param.Value) {
return x
}
if pe.Indirect || pe.Length || pe.Width || pe.Key != nil ||
pe.Slice != nil || pe.Repl != nil || pe.Exp != nil {
return x
}
if pe.Index != nil {
s.modified = true
pe.Short = true
return w
}
s.modified = true
return &Word{Parts: []WordPart{pe.Param}}
}
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 {
break
}
sub, _ := st.Cmd.(*Subshell)
if sub == nil {
break
}
s.modified = true
stmts = sub.Stmts
}
return stmts
}
func (s *simplifier) unquoteParams(x TestExpr) TestExpr {
w, _ := x.(*Word)
if w == nil || len(w.Parts) != 1 {
return x
}
dq, _ := w.Parts[0].(*DblQuoted)
if dq == nil || len(dq.Parts) != 1 {
return x
}
if _, ok := dq.Parts[0].(*ParamExp); !ok {
return x
}
s.modified = true
w.Parts = dq.Parts
return w
}
func (s *simplifier) removeParensTest(x TestExpr) TestExpr {
for {
par, _ := x.(*ParenTest)
if par == nil {
return x
}
s.modified = true
x = par.X
}
}
func (s *simplifier) removeNegateTest(x TestExpr) TestExpr {
u, _ := x.(*UnaryTest)
if u == nil || u.Op != TsNot {
return x
}
switch y := u.X.(type) {
case *UnaryTest:
switch y.Op {
case TsEmpStr:
y.Op = TsNempStr
s.modified = true
return y
case TsNempStr:
y.Op = TsEmpStr
s.modified = true
return y
case TsNot:
s.modified = true
return y.X
}
case *BinaryTest:
switch y.Op {
case TsMatch:
y.Op = TsNoMatch
s.modified = true
return y
case TsNoMatch:
y.Op = TsMatch
s.modified = true
return y
}
}
return x
}

View File

@@ -46,6 +46,11 @@ func Walk(node Node, f func(Node) bool) {
if x.Value != nil {
Walk(x.Value, f)
}
if x.Index != nil {
Walk(x.Index, f)
} else if x.Key != nil {
Walk(x.Key, f)
}
if x.Array != nil {
Walk(x.Array, f)
}
@@ -109,11 +114,11 @@ func Walk(node Node, f func(Node) bool) {
case *CmdSubst:
walkStmts(x.Stmts, f)
case *ParamExp:
if x.Param != nil {
Walk(x.Param, f)
}
Walk(x.Param, f)
if x.Index != nil {
Walk(x.Index, f)
} else if x.Key != nil {
Walk(x.Key, f)
}
if x.Repl != nil {
if x.Repl.Orig != nil {
@@ -164,6 +169,8 @@ func Walk(node Node, f func(Node) bool) {
case *ArrayElem:
if x.Index != nil {
Walk(x.Index, f)
} else if x.Key != nil {
Walk(x.Key, f)
}
Walk(x.Value, f)
case *ExtGlob: