1
0
mirror of https://github.com/go-task/task.git synced 2024-12-16 10:59:23 +02:00
task/vendor/mvdan.cc/sh/v3/syntax/simplify.go
2019-09-26 19:04:13 -03:00

250 lines
5.2 KiB
Go

// Copyright (c) 2017, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
package syntax
import "bytes"
// Simplify modifies a node to remove redundant pieces of syntax, and returns
// whether any changes were made.
//
// The changes currently applied are:
//
// Remove clearly useless parentheses $(( (expr) ))
// Remove dollars from vars in exprs (($var))
// Remove duplicate subshells $( (stmts) )
// Remove redundant quotes [[ "$var" == str ]]
// Merge negations with unary operators [[ ! -n $var ]]
// Use single quotes to shorten literals "\$foo"
func Simplify(n Node) bool {
s := simplifier{}
Walk(n, s.visit)
return s.modified
}
type simplifier struct {
modified bool
}
func (s *simplifier) visit(node Node) bool {
switch x := node.(type) {
case *Assign:
x.Index = s.removeParensArithm(x.Index)
// Don't inline params, as x[i] and x[$i] mean
// different things when x is an associative
// array; the first means "i", the second "$i".
case *ParamExp:
x.Index = s.removeParensArithm(x.Index)
// don't inline params - same as above.
if x.Slice == nil {
break
}
x.Slice.Offset = s.removeParensArithm(x.Slice.Offset)
x.Slice.Offset = s.inlineSimpleParams(x.Slice.Offset)
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)
if x.Op == TsMatchShort {
s.modified = true
x.Op = TsMatch
}
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{
Left: dq.Pos(),
Right: dq.End(),
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.Excl || pe.Length || pe.Width || 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.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
}