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