github.com/cilki/sh@v2.6.4+incompatible/expand/param.go (about) 1 // Copyright (c) 2017, Daniel Martà <mvdan@mvdan.cc> 2 // See LICENSE for licensing information 3 4 package expand 5 6 import ( 7 "fmt" 8 "regexp" 9 "sort" 10 "strconv" 11 "strings" 12 "unicode" 13 "unicode/utf8" 14 15 "mvdan.cc/sh/syntax" 16 ) 17 18 func nodeLit(node syntax.Node) string { 19 if word, ok := node.(*syntax.Word); ok { 20 return word.Lit() 21 } 22 return "" 23 } 24 25 type UnsetParameterError struct { 26 Node *syntax.ParamExp 27 Message string 28 } 29 30 func (u UnsetParameterError) Error() string { 31 return u.Message 32 } 33 34 func (cfg *Config) paramExp(pe *syntax.ParamExp) (string, error) { 35 oldParam := cfg.curParam 36 cfg.curParam = pe 37 defer func() { cfg.curParam = oldParam }() 38 39 name := pe.Param.Value 40 index := pe.Index 41 switch name { 42 case "@", "*": 43 index = &syntax.Word{Parts: []syntax.WordPart{ 44 &syntax.Lit{Value: name}, 45 }} 46 } 47 var vr Variable 48 switch name { 49 case "LINENO": 50 // This is the only parameter expansion that the environment 51 // interface cannot satisfy. 52 line := uint64(cfg.curParam.Pos().Line()) 53 vr.Value = strconv.FormatUint(line, 10) 54 default: 55 vr = cfg.Env.Get(name) 56 } 57 orig := vr 58 _, vr = vr.Resolve(cfg.Env) 59 str, err := cfg.varInd(vr, index) 60 if err != nil { 61 return "", err 62 } 63 slicePos := func(n int) int { 64 if n < 0 { 65 n = len(str) + n 66 if n < 0 { 67 n = len(str) 68 } 69 } else if n > len(str) { 70 n = len(str) 71 } 72 return n 73 } 74 elems := []string{str} 75 switch nodeLit(index) { 76 case "@", "*": 77 switch x := vr.Value.(type) { 78 case nil: 79 elems = nil 80 case []string: 81 elems = x 82 } 83 } 84 switch { 85 case pe.Length: 86 n := len(elems) 87 switch nodeLit(index) { 88 case "@", "*": 89 default: 90 n = utf8.RuneCountInString(str) 91 } 92 str = strconv.Itoa(n) 93 case pe.Excl: 94 var strs []string 95 if pe.Names != 0 { 96 strs = cfg.namesByPrefix(pe.Param.Value) 97 } else if orig.NameRef { 98 strs = append(strs, orig.Value.(string)) 99 } else if x, ok := vr.Value.([]string); ok { 100 for i, e := range x { 101 if e != "" { 102 strs = append(strs, strconv.Itoa(i)) 103 } 104 } 105 } else if x, ok := vr.Value.(map[string]string); ok { 106 for k := range x { 107 strs = append(strs, k) 108 } 109 } else if str != "" { 110 vr = cfg.Env.Get(str) 111 strs = append(strs, vr.String()) 112 } 113 sort.Strings(strs) 114 str = strings.Join(strs, " ") 115 case pe.Slice != nil: 116 if pe.Slice.Offset != nil { 117 n, err := Arithm(cfg, pe.Slice.Offset) 118 if err != nil { 119 return "", err 120 } 121 str = str[slicePos(n):] 122 } 123 if pe.Slice.Length != nil { 124 n, err := Arithm(cfg, pe.Slice.Length) 125 if err != nil { 126 return "", err 127 } 128 str = str[:slicePos(n)] 129 } 130 case pe.Repl != nil: 131 orig, err := Pattern(cfg, pe.Repl.Orig) 132 if err != nil { 133 return "", err 134 } 135 with, err := Literal(cfg, pe.Repl.With) 136 if err != nil { 137 return "", err 138 } 139 n := 1 140 if pe.Repl.All { 141 n = -1 142 } 143 locs := findAllIndex(orig, str, n) 144 buf := cfg.strBuilder() 145 last := 0 146 for _, loc := range locs { 147 buf.WriteString(str[last:loc[0]]) 148 buf.WriteString(with) 149 last = loc[1] 150 } 151 buf.WriteString(str[last:]) 152 str = buf.String() 153 case pe.Exp != nil: 154 arg, err := Literal(cfg, pe.Exp.Word) 155 if err != nil { 156 return "", err 157 } 158 switch op := pe.Exp.Op; op { 159 case syntax.SubstColPlus: 160 if str == "" { 161 break 162 } 163 fallthrough 164 case syntax.SubstPlus: 165 if vr.IsSet() { 166 str = arg 167 } 168 case syntax.SubstMinus: 169 if vr.IsSet() { 170 break 171 } 172 fallthrough 173 case syntax.SubstColMinus: 174 if str == "" { 175 str = arg 176 } 177 case syntax.SubstQuest: 178 if vr.IsSet() { 179 break 180 } 181 fallthrough 182 case syntax.SubstColQuest: 183 if str == "" { 184 return "", UnsetParameterError{ 185 Node: pe, 186 Message: arg, 187 } 188 } 189 case syntax.SubstAssgn: 190 if vr.IsSet() { 191 break 192 } 193 fallthrough 194 case syntax.SubstColAssgn: 195 if str == "" { 196 cfg.envSet(name, arg) 197 str = arg 198 } 199 case syntax.RemSmallPrefix, syntax.RemLargePrefix, 200 syntax.RemSmallSuffix, syntax.RemLargeSuffix: 201 suffix := op == syntax.RemSmallSuffix || 202 op == syntax.RemLargeSuffix 203 large := op == syntax.RemLargePrefix || 204 op == syntax.RemLargeSuffix 205 for i, elem := range elems { 206 elems[i] = removePattern(elem, arg, suffix, large) 207 } 208 str = strings.Join(elems, " ") 209 case syntax.UpperFirst, syntax.UpperAll, 210 syntax.LowerFirst, syntax.LowerAll: 211 212 caseFunc := unicode.ToLower 213 if op == syntax.UpperFirst || op == syntax.UpperAll { 214 caseFunc = unicode.ToUpper 215 } 216 all := op == syntax.UpperAll || op == syntax.LowerAll 217 218 // empty string means '?'; nothing to do there 219 expr, err := syntax.TranslatePattern(arg, false) 220 if err != nil { 221 return str, nil 222 } 223 rx := regexp.MustCompile(expr) 224 225 for i, elem := range elems { 226 rs := []rune(elem) 227 for ri, r := range rs { 228 if rx.MatchString(string(r)) { 229 rs[ri] = caseFunc(r) 230 if !all { 231 break 232 } 233 } 234 } 235 elems[i] = string(rs) 236 } 237 str = strings.Join(elems, " ") 238 case syntax.OtherParamOps: 239 switch arg { 240 case "Q": 241 str = strconv.Quote(str) 242 case "E": 243 tail := str 244 var rns []rune 245 for tail != "" { 246 var rn rune 247 rn, _, tail, _ = strconv.UnquoteChar(tail, 0) 248 rns = append(rns, rn) 249 } 250 str = string(rns) 251 case "P", "A", "a": 252 panic(fmt.Sprintf("unhandled @%s param expansion", arg)) 253 default: 254 panic(fmt.Sprintf("unexpected @%s param expansion", arg)) 255 } 256 } 257 } 258 return str, nil 259 } 260 261 func removePattern(str, pattern string, fromEnd, greedy bool) string { 262 expr, err := syntax.TranslatePattern(pattern, greedy) 263 if err != nil { 264 return str 265 } 266 switch { 267 case fromEnd && !greedy: 268 // use .* to get the right-most (shortest) match 269 expr = ".*(" + expr + ")$" 270 case fromEnd: 271 // simple suffix 272 expr = "(" + expr + ")$" 273 default: 274 // simple prefix 275 expr = "^(" + expr + ")" 276 } 277 // no need to check error as TranslatePattern returns one 278 rx := regexp.MustCompile(expr) 279 if loc := rx.FindStringSubmatchIndex(str); loc != nil { 280 // remove the original pattern (the submatch) 281 str = str[:loc[2]] + str[loc[3]:] 282 } 283 return str 284 } 285 286 func (cfg *Config) varInd(vr Variable, idx syntax.ArithmExpr) (string, error) { 287 if idx == nil { 288 return vr.String(), nil 289 } 290 switch x := vr.Value.(type) { 291 case string: 292 n, err := Arithm(cfg, idx) 293 if err != nil { 294 return "", err 295 } 296 if n == 0 { 297 return x, nil 298 } 299 case []string: 300 switch nodeLit(idx) { 301 case "@": 302 return strings.Join(x, " "), nil 303 case "*": 304 return cfg.ifsJoin(x), nil 305 } 306 i, err := Arithm(cfg, idx) 307 if err != nil { 308 return "", err 309 } 310 if len(x) > 0 { 311 return x[i], nil 312 } 313 case map[string]string: 314 switch lit := nodeLit(idx); lit { 315 case "@", "*": 316 var strs []string 317 keys := make([]string, 0, len(x)) 318 for k := range x { 319 keys = append(keys, k) 320 } 321 sort.Strings(keys) 322 for _, k := range keys { 323 strs = append(strs, x[k]) 324 } 325 if lit == "*" { 326 return cfg.ifsJoin(strs), nil 327 } 328 return strings.Join(strs, " "), nil 329 } 330 val, err := Literal(cfg, idx.(*syntax.Word)) 331 if err != nil { 332 return "", err 333 } 334 return x[val], nil 335 } 336 return "", nil 337 } 338 339 func (cfg *Config) namesByPrefix(prefix string) []string { 340 var names []string 341 cfg.Env.Each(func(name string, vr Variable) bool { 342 if strings.HasPrefix(name, prefix) { 343 names = append(names, name) 344 } 345 return true 346 }) 347 return names 348 }