1// Copyright (c) 2017, Daniel MartΓ <mvdan@mvdan.cc>
2// See LICENSE for licensing information
3
4package interp
5
6import (
7 "fmt"
8 "maps"
9 "os"
10 "runtime"
11 "slices"
12 "strconv"
13 "strings"
14
15 "mvdan.cc/sh/v3/expand"
16 "mvdan.cc/sh/v3/syntax"
17)
18
19type overlayEnviron struct {
20 parent expand.Environ
21 values map[string]expand.Variable
22
23 // We need to know if the current scope is a function's scope, because
24 // functions can modify global variables.
25 funcScope bool
26}
27
28func (o *overlayEnviron) Get(name string) expand.Variable {
29 if vr, ok := o.values[name]; ok {
30 return vr
31 }
32 return o.parent.Get(name)
33}
34
35func (o *overlayEnviron) Set(name string, vr expand.Variable) error {
36 // Manipulation of a global var inside a function.
37 if o.funcScope && !vr.Local && !o.values[name].Local {
38 // In a function, the parent environment is ours, so it's always read-write.
39 return o.parent.(expand.WriteEnviron).Set(name, vr)
40 }
41
42 if o.values == nil {
43 o.values = make(map[string]expand.Variable)
44 }
45 prev := o.Get(name)
46 if vr.Kind == expand.KeepValue {
47 vr.Kind = prev.Kind
48 vr.Str = prev.Str
49 vr.List = prev.List
50 vr.Map = prev.Map
51 } else if prev.ReadOnly {
52 return fmt.Errorf("readonly variable")
53 }
54 if !vr.IsSet() { // unsetting
55 if prev.Local {
56 vr.Local = true
57 o.values[name] = vr
58 return nil
59 }
60 delete(o.values, name)
61 }
62 // modifying the entire variable
63 vr.Local = prev.Local || vr.Local
64 o.values[name] = vr
65 return nil
66}
67
68func (o *overlayEnviron) Each(f func(name string, vr expand.Variable) bool) {
69 o.parent.Each(f)
70 for name, vr := range o.values {
71 if !f(name, vr) {
72 return
73 }
74 }
75}
76
77func execEnv(env expand.Environ) []string {
78 list := make([]string, 0, 64)
79 for name, vr := range env.Each {
80 if !vr.IsSet() {
81 // If a variable is set globally but unset in the
82 // runner, we need to ensure it's not part of the final
83 // list. Seems like zeroing the element is enough.
84 // This is a linear search, but this scenario should be
85 // rare, and the number of variables shouldn't be large.
86 for i, kv := range list {
87 if strings.HasPrefix(kv, name+"=") {
88 list[i] = ""
89 }
90 }
91 }
92 if vr.Exported && vr.Kind == expand.String {
93 list = append(list, name+"="+vr.String())
94 }
95 }
96 return list
97}
98
99func (r *Runner) lookupVar(name string) expand.Variable {
100 if name == "" {
101 panic("variable name must not be empty")
102 }
103 var vr expand.Variable
104 switch name {
105 case "#":
106 vr.Kind, vr.Str = expand.String, strconv.Itoa(len(r.Params))
107 case "@", "*":
108 vr.Kind = expand.Indexed
109 if r.Params == nil {
110 // r.Params may be nil but positional parameters always exist
111 vr.List = []string{}
112 } else {
113 vr.List = r.Params
114 }
115 case "?":
116 vr.Kind, vr.Str = expand.String, strconv.Itoa(r.lastExit)
117 case "$":
118 vr.Kind, vr.Str = expand.String, strconv.Itoa(os.Getpid())
119 case "PPID":
120 vr.Kind, vr.Str = expand.String, strconv.Itoa(os.Getppid())
121 case "DIRSTACK":
122 vr.Kind, vr.List = expand.Indexed, r.dirStack
123 case "0":
124 vr.Kind = expand.String
125 if r.filename != "" {
126 vr.Str = r.filename
127 } else {
128 vr.Str = "gosh"
129 }
130 case "1", "2", "3", "4", "5", "6", "7", "8", "9":
131 vr.Kind = expand.String
132 i := int(name[0] - '1')
133 if i < len(r.Params) {
134 vr.Str = r.Params[i]
135 } else {
136 vr.Str = ""
137 }
138 }
139 if vr.Kind != expand.Unknown {
140 vr.Set = true
141 return vr
142 }
143 if vr := r.writeEnv.Get(name); vr.Declared() {
144 return vr
145 }
146 if runtime.GOOS == "windows" {
147 upper := strings.ToUpper(name)
148 if vr := r.writeEnv.Get(upper); vr.Declared() {
149 return vr
150 }
151 }
152 return expand.Variable{}
153}
154
155func (r *Runner) envGet(name string) string {
156 return r.lookupVar(name).String()
157}
158
159func (r *Runner) delVar(name string) {
160 if err := r.writeEnv.Set(name, expand.Variable{}); err != nil {
161 r.errf("%s: %v\n", name, err)
162 r.exit = 1
163 return
164 }
165}
166
167func (r *Runner) setVarString(name, value string) {
168 r.setVar(name, expand.Variable{Set: true, Kind: expand.String, Str: value})
169}
170
171func (r *Runner) setVar(name string, vr expand.Variable) {
172 if r.opts[optAllExport] {
173 vr.Exported = true
174 }
175 if err := r.writeEnv.Set(name, vr); err != nil {
176 r.errf("%s: %v\n", name, err)
177 r.exit = 1
178 return
179 }
180}
181
182func (r *Runner) setVarWithIndex(prev expand.Variable, name string, index syntax.ArithmExpr, vr expand.Variable) {
183 prev.Set = true
184 if name2, var2 := prev.Resolve(r.writeEnv); name2 != "" {
185 name = name2
186 prev = var2
187 }
188
189 if vr.Kind == expand.String && index == nil {
190 // When assigning a string to an array, fall back to the
191 // zero value for the index.
192 switch prev.Kind {
193 case expand.Indexed:
194 index = &syntax.Word{Parts: []syntax.WordPart{
195 &syntax.Lit{Value: "0"},
196 }}
197 case expand.Associative:
198 index = &syntax.Word{Parts: []syntax.WordPart{
199 &syntax.DblQuoted{},
200 }}
201 }
202 }
203 if index == nil {
204 r.setVar(name, vr)
205 return
206 }
207
208 // from the syntax package, we know that value must be a string if index
209 // is non-nil; nested arrays are forbidden.
210 valStr := vr.Str
211
212 var list []string
213 switch prev.Kind {
214 case expand.String:
215 list = append(list, prev.Str)
216 case expand.Indexed:
217 // TODO: only clone when inside a subshell and getting a var from outside for the first time
218 list = slices.Clone(prev.List)
219 case expand.Associative:
220 // if the existing variable is already an AssocArray, try our
221 // best to convert the key to a string
222 w, ok := index.(*syntax.Word)
223 if !ok {
224 return
225 }
226 k := r.literal(w)
227
228 // TODO: only clone when inside a subshell and getting a var from outside for the first time
229 prev.Map = maps.Clone(prev.Map)
230 if prev.Map == nil {
231 prev.Map = make(map[string]string)
232 }
233 prev.Map[k] = valStr
234 r.setVar(name, prev)
235 return
236 }
237 k := r.arithm(index)
238 for len(list) < k+1 {
239 list = append(list, "")
240 }
241 list[k] = valStr
242 prev.Kind = expand.Indexed
243 prev.List = list
244 r.setVar(name, prev)
245}
246
247func (r *Runner) setFunc(name string, body *syntax.Stmt) {
248 if r.Funcs == nil {
249 r.Funcs = make(map[string]*syntax.Stmt, 4)
250 }
251 r.Funcs[name] = body
252}
253
254func stringIndex(index syntax.ArithmExpr) bool {
255 w, ok := index.(*syntax.Word)
256 if !ok || len(w.Parts) != 1 {
257 return false
258 }
259 switch w.Parts[0].(type) {
260 case *syntax.DblQuoted, *syntax.SglQuoted:
261 return true
262 }
263 return false
264}
265
266// TODO: make assignVal and [setVar] consistent with the [expand.WriteEnviron] interface
267
268func (r *Runner) assignVal(prev expand.Variable, as *syntax.Assign, valType string) expand.Variable {
269 prev.Set = true
270 if as.Value != nil {
271 s := r.literal(as.Value)
272 if !as.Append {
273 prev.Kind = expand.String
274 if valType == "-n" {
275 prev.Kind = expand.NameRef
276 }
277 prev.Str = s
278 return prev
279 }
280 switch prev.Kind {
281 case expand.String, expand.Unknown:
282 prev.Kind = expand.String
283 prev.Str += s
284 case expand.Indexed:
285 if len(prev.List) == 0 {
286 prev.List = append(prev.List, "")
287 }
288 prev.List[0] += s
289 case expand.Associative:
290 // TODO
291 }
292 return prev
293 }
294 if as.Array == nil {
295 // don't return the zero value, as that's an unset variable
296 prev.Kind = expand.String
297 if valType == "-n" {
298 prev.Kind = expand.NameRef
299 }
300 prev.Str = ""
301 return prev
302 }
303 // Array assignment.
304 elems := as.Array.Elems
305 if valType == "" {
306 valType = "-a" // indexed
307 if len(elems) > 0 && stringIndex(elems[0].Index) {
308 valType = "-A" // associative
309 }
310 }
311 if valType == "-A" {
312 amap := make(map[string]string, len(elems))
313 for _, elem := range elems {
314 k := r.literal(elem.Index.(*syntax.Word))
315 amap[k] = r.literal(elem.Value)
316 }
317 if !as.Append {
318 prev.Kind = expand.Associative
319 prev.Map = amap
320 return prev
321 }
322 // TODO
323 return prev
324 }
325 // Evaluate values for each array element.
326 elemValues := make([]struct {
327 index int
328 values []string
329 }, len(elems))
330 var index, maxIndex int
331 for i, elem := range elems {
332 if elem.Index != nil {
333 // Index resets our index with a literal value.
334 index = r.arithm(elem.Index)
335 elemValues[i].values = []string{r.literal(elem.Value)}
336 } else {
337 // Implicit index, advancing for every word.
338 elemValues[i].values = r.fields(elem.Value)
339 }
340 elemValues[i].index = index
341 index += len(elemValues[i].values)
342 maxIndex = max(maxIndex, index)
343 }
344 // Flatten down the values.
345 strs := make([]string, maxIndex)
346 for _, ev := range elemValues {
347 for i, str := range ev.values {
348 strs[ev.index+i] = str
349 }
350 }
351 if !as.Append {
352 prev.Kind = expand.Indexed
353 prev.List = strs
354 return prev
355 }
356 switch prev.Kind {
357 case expand.Unknown:
358 prev.Kind = expand.Indexed
359 prev.List = strs
360 case expand.String:
361 prev.Kind = expand.Indexed
362 prev.List = append([]string{prev.Str}, strs...)
363 case expand.Indexed:
364 prev.List = append(prev.List, strs...)
365 case expand.Associative:
366 // TODO
367 default:
368 panic(fmt.Sprintf("unhandled conversion of kind %d", prev.Kind))
369 }
370 return prev
371}