github.com/cilki/sh@v2.6.4+incompatible/syntax/simplify.go (about) 1 // Copyright (c) 2017, Daniel Martà <mvdan@mvdan.cc> 2 // See LICENSE for licensing information 3 4 package syntax 5 6 import "bytes" 7 8 // Simplify simplifies a given program and returns whether any changes 9 // were made. 10 // 11 // The changes currently applied are: 12 // 13 // Remove clearly useless parentheses $(( (expr) )) 14 // Remove dollars from vars in exprs (($var)) 15 // Remove duplicate subshells $( (stmts) ) 16 // Remove redundant quotes [[ "$var" == str ]] 17 // Merge negations with unary operators [[ ! -n $var ]] 18 // Use single quotes to shorten literals "\$foo" 19 func Simplify(n Node) bool { 20 s := simplifier{} 21 Walk(n, s.visit) 22 return s.modified 23 } 24 25 type simplifier struct { 26 modified bool 27 } 28 29 func (s *simplifier) visit(node Node) bool { 30 switch x := node.(type) { 31 case *Assign: 32 x.Index = s.removeParensArithm(x.Index) 33 // Don't inline params, as x[i] and x[$i] mean 34 // different things when x is an associative 35 // array; the first means "i", the second "$i". 36 case *ParamExp: 37 x.Index = s.removeParensArithm(x.Index) 38 // don't inline params - same as above. 39 40 if x.Slice == nil { 41 break 42 } 43 x.Slice.Offset = s.removeParensArithm(x.Slice.Offset) 44 x.Slice.Offset = s.inlineSimpleParams(x.Slice.Offset) 45 x.Slice.Length = s.removeParensArithm(x.Slice.Length) 46 x.Slice.Length = s.inlineSimpleParams(x.Slice.Length) 47 case *ArithmExp: 48 x.X = s.removeParensArithm(x.X) 49 x.X = s.inlineSimpleParams(x.X) 50 case *ArithmCmd: 51 x.X = s.removeParensArithm(x.X) 52 x.X = s.inlineSimpleParams(x.X) 53 case *ParenArithm: 54 x.X = s.removeParensArithm(x.X) 55 x.X = s.inlineSimpleParams(x.X) 56 case *BinaryArithm: 57 x.X = s.inlineSimpleParams(x.X) 58 x.Y = s.inlineSimpleParams(x.Y) 59 case *CmdSubst: 60 x.Stmts = s.inlineSubshell(x.Stmts) 61 case *Subshell: 62 x.Stmts = s.inlineSubshell(x.Stmts) 63 case *Word: 64 x.Parts = s.simplifyWord(x.Parts) 65 case *TestClause: 66 x.X = s.removeParensTest(x.X) 67 x.X = s.removeNegateTest(x.X) 68 case *ParenTest: 69 x.X = s.removeParensTest(x.X) 70 x.X = s.removeNegateTest(x.X) 71 case *BinaryTest: 72 x.X = s.unquoteParams(x.X) 73 x.X = s.removeNegateTest(x.X) 74 switch x.Op { 75 case TsMatch, TsNoMatch: 76 // unquoting enables globbing 77 default: 78 x.Y = s.unquoteParams(x.Y) 79 } 80 x.Y = s.removeNegateTest(x.Y) 81 case *UnaryTest: 82 x.X = s.unquoteParams(x.X) 83 } 84 return true 85 } 86 87 func (s *simplifier) simplifyWord(wps []WordPart) []WordPart { 88 parts: 89 for i, wp := range wps { 90 dq, _ := wp.(*DblQuoted) 91 if dq == nil || len(dq.Parts) != 1 { 92 break 93 } 94 lit, _ := dq.Parts[0].(*Lit) 95 if lit == nil { 96 break 97 } 98 var buf bytes.Buffer 99 escaped := false 100 for _, r := range lit.Value { 101 switch r { 102 case '\\': 103 escaped = !escaped 104 if escaped { 105 continue 106 } 107 case '\'': 108 continue parts 109 case '$', '"', '`': 110 escaped = false 111 default: 112 if escaped { 113 continue parts 114 } 115 escaped = false 116 } 117 buf.WriteRune(r) 118 } 119 newVal := buf.String() 120 if newVal == lit.Value { 121 break 122 } 123 s.modified = true 124 wps[i] = &SglQuoted{ 125 Left: dq.Pos(), 126 Right: dq.End(), 127 Dollar: dq.Dollar, 128 Value: newVal, 129 } 130 } 131 return wps 132 } 133 134 func (s *simplifier) removeParensArithm(x ArithmExpr) ArithmExpr { 135 for { 136 par, _ := x.(*ParenArithm) 137 if par == nil { 138 return x 139 } 140 s.modified = true 141 x = par.X 142 } 143 } 144 145 func (s *simplifier) inlineSimpleParams(x ArithmExpr) ArithmExpr { 146 w, _ := x.(*Word) 147 if w == nil || len(w.Parts) != 1 { 148 return x 149 } 150 pe, _ := w.Parts[0].(*ParamExp) 151 if pe == nil || !ValidName(pe.Param.Value) { 152 return x 153 } 154 if pe.Excl || pe.Length || pe.Width || pe.Slice != nil || 155 pe.Repl != nil || pe.Exp != nil { 156 return x 157 } 158 if pe.Index != nil { 159 s.modified = true 160 pe.Short = true 161 return w 162 } 163 s.modified = true 164 return &Word{Parts: []WordPart{pe.Param}} 165 } 166 167 func (s *simplifier) inlineSubshell(stmts []*Stmt) []*Stmt { 168 for len(stmts) == 1 { 169 st := stmts[0] 170 if st.Negated || st.Background || st.Coprocess || 171 len(st.Redirs) > 0 { 172 break 173 } 174 sub, _ := st.Cmd.(*Subshell) 175 if sub == nil { 176 break 177 } 178 s.modified = true 179 stmts = sub.Stmts 180 } 181 return stmts 182 } 183 184 func (s *simplifier) unquoteParams(x TestExpr) TestExpr { 185 w, _ := x.(*Word) 186 if w == nil || len(w.Parts) != 1 { 187 return x 188 } 189 dq, _ := w.Parts[0].(*DblQuoted) 190 if dq == nil || len(dq.Parts) != 1 { 191 return x 192 } 193 if _, ok := dq.Parts[0].(*ParamExp); !ok { 194 return x 195 } 196 s.modified = true 197 w.Parts = dq.Parts 198 return w 199 } 200 201 func (s *simplifier) removeParensTest(x TestExpr) TestExpr { 202 for { 203 par, _ := x.(*ParenTest) 204 if par == nil { 205 return x 206 } 207 s.modified = true 208 x = par.X 209 } 210 } 211 212 func (s *simplifier) removeNegateTest(x TestExpr) TestExpr { 213 u, _ := x.(*UnaryTest) 214 if u == nil || u.Op != TsNot { 215 return x 216 } 217 switch y := u.X.(type) { 218 case *UnaryTest: 219 switch y.Op { 220 case TsEmpStr: 221 y.Op = TsNempStr 222 s.modified = true 223 return y 224 case TsNempStr: 225 y.Op = TsEmpStr 226 s.modified = true 227 return y 228 case TsNot: 229 s.modified = true 230 return y.X 231 } 232 case *BinaryTest: 233 switch y.Op { 234 case TsMatch: 235 y.Op = TsNoMatch 236 s.modified = true 237 return y 238 case TsNoMatch: 239 y.Op = TsMatch 240 s.modified = true 241 return y 242 } 243 } 244 return x 245 }