1// Copyright (c) 2017, Daniel MartΓ <mvdan@mvdan.cc>
2// See LICENSE for licensing information
3
4package expand
5
6import (
7 "fmt"
8 "maps"
9 "regexp"
10 "slices"
11 "strconv"
12 "strings"
13 "unicode"
14 "unicode/utf8"
15
16 "mvdan.cc/sh/v3/pattern"
17 "mvdan.cc/sh/v3/syntax"
18)
19
20func nodeLit(node syntax.Node) string {
21 if word, ok := node.(*syntax.Word); ok {
22 return word.Lit()
23 }
24 return ""
25}
26
27type UnsetParameterError struct {
28 Node *syntax.ParamExp
29 Message string
30}
31
32func (u UnsetParameterError) Error() string {
33 return fmt.Sprintf("%s: %s", u.Node.Param.Value, u.Message)
34}
35
36func overridingUnset(pe *syntax.ParamExp) bool {
37 if pe.Exp == nil {
38 return false
39 }
40 switch pe.Exp.Op {
41 case syntax.AlternateUnset, syntax.AlternateUnsetOrNull,
42 syntax.DefaultUnset, syntax.DefaultUnsetOrNull,
43 syntax.ErrorUnset, syntax.ErrorUnsetOrNull,
44 syntax.AssignUnset, syntax.AssignUnsetOrNull:
45 return true
46 }
47 return false
48}
49
50func (cfg *Config) paramExp(pe *syntax.ParamExp) (string, error) {
51 oldParam := cfg.curParam
52 cfg.curParam = pe
53 defer func() { cfg.curParam = oldParam }()
54
55 name := pe.Param.Value
56 index := pe.Index
57 switch name {
58 case "@", "*":
59 index = &syntax.Word{Parts: []syntax.WordPart{
60 &syntax.Lit{Value: name},
61 }}
62 }
63 var vr Variable
64 switch name {
65 case "LINENO":
66 // This is the only parameter expansion that the environment
67 // interface cannot satisfy.
68 line := uint64(cfg.curParam.Pos().Line())
69 vr = Variable{Set: true, Kind: String, Str: strconv.FormatUint(line, 10)}
70 default:
71 vr = cfg.Env.Get(name)
72 }
73 orig := vr
74 _, vr = vr.Resolve(cfg.Env)
75 if cfg.NoUnset && !vr.IsSet() && !overridingUnset(pe) {
76 return "", UnsetParameterError{
77 Node: pe,
78 Message: "unbound variable",
79 }
80 }
81
82 var sliceOffset, sliceLen int
83 if pe.Slice != nil {
84 var err error
85 if pe.Slice.Offset != nil {
86 sliceOffset, err = Arithm(cfg, pe.Slice.Offset)
87 if err != nil {
88 return "", err
89 }
90 }
91 if pe.Slice.Length != nil {
92 sliceLen, err = Arithm(cfg, pe.Slice.Length)
93 if err != nil {
94 return "", err
95 }
96 }
97 }
98
99 var (
100 str string
101 elems []string
102
103 indexAllElements bool // true if var has been accessed with * or @ index
104 callVarInd = true
105 )
106
107 switch nodeLit(index) {
108 case "@", "*":
109 switch vr.Kind {
110 case Unknown:
111 elems = nil
112 indexAllElements = true
113 case Indexed:
114 indexAllElements = true
115 callVarInd = false
116 elems = vr.List
117 slicePos := func(n int) int {
118 if n < 0 {
119 n = len(elems) + n
120 if n < 0 {
121 n = len(elems)
122 }
123 } else if n > len(elems) {
124 n = len(elems)
125 }
126 return n
127 }
128 if pe.Slice != nil && pe.Slice.Offset != nil {
129 elems = elems[slicePos(sliceOffset):]
130 }
131 if pe.Slice != nil && pe.Slice.Length != nil {
132 elems = elems[:slicePos(sliceLen)]
133 }
134 str = strings.Join(elems, " ")
135 }
136 }
137 if callVarInd {
138 var err error
139 str, err = cfg.varInd(vr, index)
140 if err != nil {
141 return "", err
142 }
143 }
144 if !indexAllElements {
145 elems = []string{str}
146 }
147
148 switch {
149 case pe.Length:
150 n := len(elems)
151 switch nodeLit(index) {
152 case "@", "*":
153 default:
154 n = utf8.RuneCountInString(str)
155 }
156 str = strconv.Itoa(n)
157 case pe.Excl:
158 var strs []string
159 switch {
160 case pe.Names != 0:
161 strs = cfg.namesByPrefix(pe.Param.Value)
162 case orig.Kind == NameRef:
163 strs = append(strs, orig.Str)
164 case pe.Index != nil && vr.Kind == Indexed:
165 for i, e := range vr.List {
166 if e != "" {
167 strs = append(strs, strconv.Itoa(i))
168 }
169 }
170 case pe.Index != nil && vr.Kind == Associative:
171 strs = slices.AppendSeq(strs, maps.Keys(vr.Map))
172 case !vr.IsSet():
173 return "", fmt.Errorf("invalid indirect expansion")
174 case str == "":
175 return "", nil
176 default:
177 vr = cfg.Env.Get(str)
178 strs = append(strs, vr.String())
179 }
180 slices.Sort(strs)
181 str = strings.Join(strs, " ")
182 case pe.Slice != nil:
183 if callVarInd {
184 slicePos := func(n int) int {
185 if n < 0 {
186 n = len(str) + n
187 if n < 0 {
188 n = len(str)
189 }
190 } else if n > len(str) {
191 n = len(str)
192 }
193 return n
194 }
195 if pe.Slice.Offset != nil {
196 str = str[slicePos(sliceOffset):]
197 }
198 if pe.Slice.Length != nil {
199 str = str[:slicePos(sliceLen)]
200 }
201 } // else, elems are already sliced
202 case pe.Repl != nil:
203 orig, err := Pattern(cfg, pe.Repl.Orig)
204 if err != nil {
205 return "", err
206 }
207 if orig == "" {
208 break // nothing to replace
209 }
210 with, err := Literal(cfg, pe.Repl.With)
211 if err != nil {
212 return "", err
213 }
214 n := 1
215 if pe.Repl.All {
216 n = -1
217 }
218 locs := findAllIndex(orig, str, n)
219 sb := cfg.strBuilder()
220 last := 0
221 for _, loc := range locs {
222 sb.WriteString(str[last:loc[0]])
223 sb.WriteString(with)
224 last = loc[1]
225 }
226 sb.WriteString(str[last:])
227 str = sb.String()
228 case pe.Exp != nil:
229 arg, err := Literal(cfg, pe.Exp.Word)
230 if err != nil {
231 return "", err
232 }
233 switch op := pe.Exp.Op; op {
234 case syntax.AlternateUnsetOrNull:
235 if str == "" {
236 break
237 }
238 fallthrough
239 case syntax.AlternateUnset:
240 if vr.IsSet() {
241 str = arg
242 }
243 case syntax.DefaultUnset:
244 if vr.IsSet() {
245 break
246 }
247 fallthrough
248 case syntax.DefaultUnsetOrNull:
249 if str == "" {
250 str = arg
251 }
252 case syntax.ErrorUnset:
253 if vr.IsSet() {
254 break
255 }
256 fallthrough
257 case syntax.ErrorUnsetOrNull:
258 if str == "" {
259 return "", UnsetParameterError{
260 Node: pe,
261 Message: arg,
262 }
263 }
264 case syntax.AssignUnset:
265 if vr.IsSet() {
266 break
267 }
268 fallthrough
269 case syntax.AssignUnsetOrNull:
270 if str == "" {
271 if err := cfg.envSet(name, arg); err != nil {
272 return "", err
273 }
274 str = arg
275 }
276 case syntax.RemSmallPrefix, syntax.RemLargePrefix,
277 syntax.RemSmallSuffix, syntax.RemLargeSuffix:
278 suffix := op == syntax.RemSmallSuffix || op == syntax.RemLargeSuffix
279 small := op == syntax.RemSmallPrefix || op == syntax.RemSmallSuffix
280 for i, elem := range elems {
281 elems[i] = removePattern(elem, arg, suffix, small)
282 }
283 str = strings.Join(elems, " ")
284 case syntax.UpperFirst, syntax.UpperAll,
285 syntax.LowerFirst, syntax.LowerAll:
286
287 caseFunc := unicode.ToLower
288 if op == syntax.UpperFirst || op == syntax.UpperAll {
289 caseFunc = unicode.ToUpper
290 }
291 all := op == syntax.UpperAll || op == syntax.LowerAll
292
293 // empty string means '?'; nothing to do there
294 expr, err := pattern.Regexp(arg, 0)
295 if err != nil {
296 return str, nil
297 }
298 rx := regexp.MustCompile(expr)
299
300 for i, elem := range elems {
301 rs := []rune(elem)
302 for ri, r := range rs {
303 if rx.MatchString(string(r)) {
304 rs[ri] = caseFunc(r)
305 if !all {
306 break
307 }
308 }
309 }
310 elems[i] = string(rs)
311 }
312 str = strings.Join(elems, " ")
313 case syntax.OtherParamOps:
314 switch arg {
315 case "Q":
316 str, err = syntax.Quote(str, syntax.LangBash)
317 if err != nil {
318 // Is this even possible? If a user runs into this panic,
319 // it's most likely a bug we need to fix.
320 panic(err)
321 }
322 case "E":
323 tail := str
324 var rns []rune
325 for tail != "" {
326 var rn rune
327 rn, _, tail, _ = strconv.UnquoteChar(tail, 0)
328 rns = append(rns, rn)
329 }
330 str = string(rns)
331 case "P", "A", "a":
332 panic(fmt.Sprintf("unhandled @%s param expansion", arg))
333 default:
334 panic(fmt.Sprintf("unexpected @%s param expansion", arg))
335 }
336 }
337 }
338 return str, nil
339}
340
341func removePattern(str, pat string, fromEnd, shortest bool) string {
342 var mode pattern.Mode
343 if shortest {
344 mode |= pattern.Shortest
345 }
346 expr, err := pattern.Regexp(pat, mode)
347 if err != nil {
348 return str
349 }
350 switch {
351 case fromEnd && shortest:
352 // use .* to get the right-most shortest match
353 expr = ".*(" + expr + ")$"
354 case fromEnd:
355 // simple suffix
356 expr = "(" + expr + ")$"
357 default:
358 // simple prefix
359 expr = "^(" + expr + ")"
360 }
361 // no need to check error as Translate returns one
362 rx := regexp.MustCompile(expr)
363 if loc := rx.FindStringSubmatchIndex(str); loc != nil {
364 // remove the original pattern (the submatch)
365 str = str[:loc[2]] + str[loc[3]:]
366 }
367 return str
368}
369
370func (cfg *Config) varInd(vr Variable, idx syntax.ArithmExpr) (string, error) {
371 if idx == nil {
372 return vr.String(), nil
373 }
374 switch vr.Kind {
375 case String:
376 n, err := Arithm(cfg, idx)
377 if err != nil {
378 return "", err
379 }
380 if n == 0 {
381 return vr.Str, nil
382 }
383 case Indexed:
384 switch nodeLit(idx) {
385 case "*", "@":
386 return strings.Join(vr.List, " "), nil
387 }
388 i, err := Arithm(cfg, idx)
389 if err != nil {
390 return "", err
391 }
392 if i < 0 {
393 return "", fmt.Errorf("negative array index")
394 }
395 if i < len(vr.List) {
396 return vr.List[i], nil
397 }
398 case Associative:
399 switch lit := nodeLit(idx); lit {
400 case "@", "*":
401 strs := slices.Sorted(maps.Values(vr.Map))
402 if lit == "*" {
403 return cfg.ifsJoin(strs), nil
404 }
405 return strings.Join(strs, " "), nil
406 }
407 val, err := Literal(cfg, idx.(*syntax.Word))
408 if err != nil {
409 return "", err
410 }
411 return vr.Map[val], nil
412 }
413 return "", nil
414}
415
416func (cfg *Config) namesByPrefix(prefix string) []string {
417 var names []string
418 for name := range cfg.Env.Each {
419 if strings.HasPrefix(name, prefix) {
420 names = append(names, name)
421 }
422 }
423 return names
424}