2018-01-03 19:12:40 +02:00
|
|
|
// Copyright (c) 2017, Daniel Martí <mvdan@mvdan.cc>
|
|
|
|
// See LICENSE for licensing information
|
|
|
|
|
|
|
|
package interp
|
|
|
|
|
|
|
|
import (
|
2018-09-01 16:00:49 +02:00
|
|
|
"context"
|
2018-04-28 20:35:15 +02:00
|
|
|
"fmt"
|
2018-01-03 19:12:40 +02:00
|
|
|
"runtime"
|
|
|
|
"sort"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"mvdan.cc/sh/syntax"
|
|
|
|
)
|
|
|
|
|
2018-04-28 20:35:15 +02:00
|
|
|
type Environ interface {
|
|
|
|
Get(name string) (value string, exists bool)
|
|
|
|
Set(name, value string)
|
|
|
|
Delete(name string)
|
|
|
|
Names() []string
|
|
|
|
Copy() Environ
|
|
|
|
}
|
|
|
|
|
|
|
|
type mapEnviron struct {
|
|
|
|
names []string
|
|
|
|
values map[string]string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *mapEnviron) Get(name string) (string, bool) {
|
|
|
|
val, ok := m.values[name]
|
|
|
|
return val, ok
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *mapEnviron) Set(name, value string) {
|
|
|
|
_, ok := m.values[name]
|
|
|
|
if !ok {
|
|
|
|
m.names = append(m.names, name)
|
|
|
|
sort.Strings(m.names)
|
|
|
|
}
|
|
|
|
m.values[name] = value
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *mapEnviron) Delete(name string) {
|
|
|
|
if _, ok := m.values[name]; !ok {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
delete(m.values, name)
|
|
|
|
for i, iname := range m.names {
|
|
|
|
if iname == name {
|
|
|
|
m.names = append(m.names[:i], m.names[i+1:]...)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *mapEnviron) Names() []string {
|
|
|
|
return m.names
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *mapEnviron) Copy() Environ {
|
|
|
|
m2 := &mapEnviron{
|
|
|
|
names: make([]string, len(m.names)),
|
|
|
|
values: make(map[string]string, len(m.values)),
|
|
|
|
}
|
|
|
|
copy(m2.names, m.names)
|
|
|
|
for name, val := range m.values {
|
|
|
|
m2.values[name] = val
|
|
|
|
}
|
|
|
|
return m2
|
|
|
|
}
|
|
|
|
|
|
|
|
func execEnv(env Environ) []string {
|
|
|
|
names := env.Names()
|
|
|
|
list := make([]string, len(names))
|
|
|
|
for i, name := range names {
|
|
|
|
val, _ := env.Get(name)
|
|
|
|
list[i] = name + "=" + val
|
|
|
|
}
|
|
|
|
return list
|
|
|
|
}
|
|
|
|
|
|
|
|
func EnvFromList(list []string) (Environ, error) {
|
|
|
|
m := mapEnviron{
|
|
|
|
names: make([]string, 0, len(list)),
|
|
|
|
values: make(map[string]string, len(list)),
|
|
|
|
}
|
|
|
|
for _, kv := range list {
|
|
|
|
i := strings.IndexByte(kv, '=')
|
|
|
|
if i < 0 {
|
|
|
|
return nil, fmt.Errorf("env not in the form key=value: %q", kv)
|
|
|
|
}
|
|
|
|
name, val := kv[:i], kv[i+1:]
|
|
|
|
if runtime.GOOS == "windows" {
|
|
|
|
name = strings.ToUpper(name)
|
|
|
|
}
|
|
|
|
m.names = append(m.names, name)
|
|
|
|
m.values[name] = val
|
|
|
|
}
|
|
|
|
sort.Strings(m.names)
|
|
|
|
return &m, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type FuncEnviron func(string) string
|
|
|
|
|
|
|
|
func (f FuncEnviron) Get(name string) (string, bool) {
|
|
|
|
val := f(name)
|
|
|
|
return val, val != ""
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f FuncEnviron) Set(name, value string) {}
|
|
|
|
func (f FuncEnviron) Delete(name string) {}
|
|
|
|
func (f FuncEnviron) Names() []string { return nil }
|
|
|
|
func (f FuncEnviron) Copy() Environ { return f }
|
|
|
|
|
2018-01-03 19:12:40 +02:00
|
|
|
type Variable struct {
|
|
|
|
Local bool
|
|
|
|
Exported bool
|
|
|
|
ReadOnly bool
|
|
|
|
NameRef bool
|
|
|
|
Value VarValue
|
|
|
|
}
|
|
|
|
|
|
|
|
// VarValue is one of:
|
|
|
|
//
|
|
|
|
// StringVal
|
|
|
|
// IndexArray
|
|
|
|
// AssocArray
|
|
|
|
type VarValue interface{}
|
|
|
|
|
|
|
|
type StringVal string
|
|
|
|
|
|
|
|
type IndexArray []string
|
|
|
|
|
|
|
|
type AssocArray map[string]string
|
|
|
|
|
|
|
|
func (r *Runner) lookupVar(name string) (Variable, bool) {
|
|
|
|
if name == "" {
|
|
|
|
panic("variable name must not be empty")
|
|
|
|
}
|
|
|
|
if val, e := r.cmdVars[name]; e {
|
|
|
|
return Variable{Value: StringVal(val)}, true
|
|
|
|
}
|
|
|
|
if vr, e := r.funcVars[name]; e {
|
|
|
|
return vr, true
|
|
|
|
}
|
|
|
|
if vr, e := r.Vars[name]; e {
|
|
|
|
return vr, true
|
|
|
|
}
|
2018-04-28 20:35:15 +02:00
|
|
|
if str, e := r.Env.Get(name); e {
|
2018-01-03 19:12:40 +02:00
|
|
|
return Variable{Value: StringVal(str)}, true
|
|
|
|
}
|
|
|
|
if runtime.GOOS == "windows" {
|
|
|
|
upper := strings.ToUpper(name)
|
2018-04-28 20:35:15 +02:00
|
|
|
if str, e := r.Env.Get(upper); e {
|
2018-01-03 19:12:40 +02:00
|
|
|
return Variable{Value: StringVal(str)}, true
|
|
|
|
}
|
|
|
|
}
|
2018-04-28 20:35:15 +02:00
|
|
|
if r.opts[optNoUnset] {
|
2018-01-03 19:12:40 +02:00
|
|
|
r.errf("%s: unbound variable\n", name)
|
2018-09-01 16:00:49 +02:00
|
|
|
r.setErr(ShellExitStatus(1))
|
2018-01-03 19:12:40 +02:00
|
|
|
}
|
|
|
|
return Variable{}, false
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *Runner) getVar(name string) string {
|
|
|
|
val, _ := r.lookupVar(name)
|
|
|
|
return r.varStr(val, 0)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *Runner) delVar(name string) {
|
|
|
|
val, _ := r.lookupVar(name)
|
|
|
|
if val.ReadOnly {
|
|
|
|
r.errf("%s: readonly variable\n", name)
|
|
|
|
r.exit = 1
|
|
|
|
return
|
|
|
|
}
|
|
|
|
delete(r.Vars, name)
|
|
|
|
delete(r.funcVars, name)
|
|
|
|
delete(r.cmdVars, name)
|
2018-04-28 20:35:15 +02:00
|
|
|
r.Env.Delete(name)
|
2018-01-03 19:12:40 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// maxNameRefDepth defines the maximum number of times to follow
|
|
|
|
// references when expanding a variable. Otherwise, simple name
|
|
|
|
// reference loops could crash the interpreter quite easily.
|
|
|
|
const maxNameRefDepth = 100
|
|
|
|
|
|
|
|
func (r *Runner) varStr(vr Variable, depth int) string {
|
|
|
|
if depth > maxNameRefDepth {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
switch x := vr.Value.(type) {
|
|
|
|
case StringVal:
|
|
|
|
if vr.NameRef {
|
|
|
|
vr, _ = r.lookupVar(string(x))
|
|
|
|
return r.varStr(vr, depth+1)
|
|
|
|
}
|
|
|
|
return string(x)
|
|
|
|
case IndexArray:
|
|
|
|
if len(x) > 0 {
|
|
|
|
return x[0]
|
|
|
|
}
|
|
|
|
case AssocArray:
|
|
|
|
// nothing to do
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
2018-09-01 16:00:49 +02:00
|
|
|
func (r *Runner) varInd(ctx context.Context, vr Variable, e syntax.ArithmExpr, depth int) string {
|
2018-01-03 19:12:40 +02:00
|
|
|
if depth > maxNameRefDepth {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
switch x := vr.Value.(type) {
|
|
|
|
case StringVal:
|
|
|
|
if vr.NameRef {
|
|
|
|
vr, _ = r.lookupVar(string(x))
|
2018-09-01 16:00:49 +02:00
|
|
|
return r.varInd(ctx, vr, e, depth+1)
|
2018-01-03 19:12:40 +02:00
|
|
|
}
|
2018-09-01 16:00:49 +02:00
|
|
|
if r.arithm(ctx, e) == 0 {
|
2018-01-03 19:12:40 +02:00
|
|
|
return string(x)
|
|
|
|
}
|
|
|
|
case IndexArray:
|
|
|
|
switch anyOfLit(e, "@", "*") {
|
|
|
|
case "@":
|
|
|
|
return strings.Join(x, " ")
|
|
|
|
case "*":
|
|
|
|
return strings.Join(x, r.ifsJoin)
|
|
|
|
}
|
2018-09-01 16:00:49 +02:00
|
|
|
i := r.arithm(ctx, e)
|
2018-01-03 19:12:40 +02:00
|
|
|
if len(x) > 0 {
|
|
|
|
return x[i]
|
|
|
|
}
|
|
|
|
case AssocArray:
|
|
|
|
if lit := anyOfLit(e, "@", "*"); lit != "" {
|
|
|
|
var strs IndexArray
|
|
|
|
keys := make([]string, 0, len(x))
|
|
|
|
for k := range x {
|
|
|
|
keys = append(keys, k)
|
|
|
|
}
|
|
|
|
sort.Strings(keys)
|
|
|
|
for _, k := range keys {
|
|
|
|
strs = append(strs, x[k])
|
|
|
|
}
|
|
|
|
if lit == "*" {
|
|
|
|
return strings.Join(strs, r.ifsJoin)
|
|
|
|
}
|
|
|
|
return strings.Join(strs, " ")
|
|
|
|
}
|
2018-09-01 16:00:49 +02:00
|
|
|
return x[r.loneWord(ctx, e.(*syntax.Word))]
|
2018-01-03 19:12:40 +02:00
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
2018-09-01 16:00:49 +02:00
|
|
|
func (r *Runner) setVarString(ctx context.Context, name, val string) {
|
|
|
|
r.setVar(ctx, name, nil, Variable{Value: StringVal(val)})
|
2018-01-03 19:12:40 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (r *Runner) setVarInternal(name string, vr Variable) {
|
|
|
|
if _, ok := vr.Value.(StringVal); ok {
|
2018-04-28 20:35:15 +02:00
|
|
|
if r.opts[optAllExport] {
|
2018-01-03 19:12:40 +02:00
|
|
|
vr.Exported = true
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
vr.Exported = false
|
|
|
|
}
|
|
|
|
if vr.Local {
|
|
|
|
if r.funcVars == nil {
|
|
|
|
r.funcVars = make(map[string]Variable)
|
|
|
|
}
|
|
|
|
r.funcVars[name] = vr
|
|
|
|
} else {
|
|
|
|
r.Vars[name] = vr
|
|
|
|
}
|
|
|
|
if name == "IFS" {
|
|
|
|
r.ifsUpdated()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-01 16:00:49 +02:00
|
|
|
func (r *Runner) setVar(ctx context.Context, name string, index syntax.ArithmExpr, vr Variable) {
|
2018-01-03 19:12:40 +02:00
|
|
|
cur, _ := r.lookupVar(name)
|
|
|
|
if cur.ReadOnly {
|
|
|
|
r.errf("%s: readonly variable\n", name)
|
|
|
|
r.exit = 1
|
|
|
|
return
|
|
|
|
}
|
|
|
|
_, isIndexArray := cur.Value.(IndexArray)
|
|
|
|
_, isAssocArray := cur.Value.(AssocArray)
|
|
|
|
|
|
|
|
if _, ok := vr.Value.(StringVal); ok && index == nil {
|
|
|
|
// When assigning a string to an array, fall back to the
|
|
|
|
// zero value for the index.
|
|
|
|
if isIndexArray {
|
|
|
|
index = &syntax.Word{Parts: []syntax.WordPart{
|
|
|
|
&syntax.Lit{Value: "0"},
|
|
|
|
}}
|
|
|
|
} else if isAssocArray {
|
|
|
|
index = &syntax.Word{Parts: []syntax.WordPart{
|
|
|
|
&syntax.DblQuoted{},
|
|
|
|
}}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if index == nil {
|
|
|
|
r.setVarInternal(name, vr)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// from the syntax package, we know that val must be a string if
|
|
|
|
// index is non-nil; nested arrays are forbidden.
|
|
|
|
valStr := string(vr.Value.(StringVal))
|
|
|
|
|
|
|
|
// if the existing variable is already an AssocArray, try our best
|
|
|
|
// to convert the key to a string
|
2018-02-12 02:02:22 +02:00
|
|
|
if isAssocArray {
|
|
|
|
amap := cur.Value.(AssocArray)
|
2018-01-03 19:12:40 +02:00
|
|
|
w, ok := index.(*syntax.Word)
|
|
|
|
if !ok {
|
|
|
|
return
|
|
|
|
}
|
2018-09-01 16:00:49 +02:00
|
|
|
k := r.loneWord(ctx, w)
|
2018-01-03 19:12:40 +02:00
|
|
|
amap[k] = valStr
|
|
|
|
cur.Value = amap
|
|
|
|
r.setVarInternal(name, cur)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
var list IndexArray
|
|
|
|
switch x := cur.Value.(type) {
|
|
|
|
case StringVal:
|
|
|
|
list = append(list, string(x))
|
|
|
|
case IndexArray:
|
|
|
|
list = x
|
|
|
|
case AssocArray: // done above
|
|
|
|
}
|
2018-09-01 16:00:49 +02:00
|
|
|
k := r.arithm(ctx, index)
|
2018-01-03 19:12:40 +02:00
|
|
|
for len(list) < k+1 {
|
|
|
|
list = append(list, "")
|
|
|
|
}
|
|
|
|
list[k] = valStr
|
|
|
|
cur.Value = list
|
|
|
|
r.setVarInternal(name, cur)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *Runner) setFunc(name string, body *syntax.Stmt) {
|
|
|
|
if r.Funcs == nil {
|
|
|
|
r.Funcs = make(map[string]*syntax.Stmt, 4)
|
|
|
|
}
|
|
|
|
r.Funcs[name] = body
|
|
|
|
}
|
|
|
|
|
|
|
|
func stringIndex(index syntax.ArithmExpr) bool {
|
|
|
|
w, ok := index.(*syntax.Word)
|
|
|
|
if !ok || len(w.Parts) != 1 {
|
|
|
|
return false
|
|
|
|
}
|
2018-01-21 13:39:15 +02:00
|
|
|
switch w.Parts[0].(type) {
|
|
|
|
case *syntax.DblQuoted, *syntax.SglQuoted:
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
2018-01-03 19:12:40 +02:00
|
|
|
}
|
|
|
|
|
2018-09-01 16:00:49 +02:00
|
|
|
func (r *Runner) assignVal(ctx context.Context, as *syntax.Assign, valType string) VarValue {
|
2018-01-03 19:12:40 +02:00
|
|
|
prev, prevOk := r.lookupVar(as.Name.Value)
|
|
|
|
if as.Naked {
|
|
|
|
return prev.Value
|
|
|
|
}
|
|
|
|
if as.Value != nil {
|
2018-09-01 16:00:49 +02:00
|
|
|
s := r.loneWord(ctx, as.Value)
|
2018-01-03 19:12:40 +02:00
|
|
|
if !as.Append || !prevOk {
|
|
|
|
return StringVal(s)
|
|
|
|
}
|
|
|
|
switch x := prev.Value.(type) {
|
|
|
|
case StringVal:
|
|
|
|
return x + StringVal(s)
|
|
|
|
case IndexArray:
|
|
|
|
if len(x) == 0 {
|
|
|
|
x = append(x, "")
|
|
|
|
}
|
|
|
|
x[0] += s
|
|
|
|
return x
|
|
|
|
case AssocArray:
|
|
|
|
// TODO
|
|
|
|
}
|
|
|
|
return StringVal(s)
|
|
|
|
}
|
|
|
|
if as.Array == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
elems := as.Array.Elems
|
|
|
|
if valType == "" {
|
|
|
|
if len(elems) == 0 || !stringIndex(elems[0].Index) {
|
|
|
|
valType = "-a" // indexed
|
|
|
|
} else {
|
|
|
|
valType = "-A" // associative
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if valType == "-A" {
|
|
|
|
// associative array
|
|
|
|
amap := AssocArray(make(map[string]string, len(elems)))
|
|
|
|
for _, elem := range elems {
|
2018-09-01 16:00:49 +02:00
|
|
|
k := r.loneWord(ctx, elem.Index.(*syntax.Word))
|
|
|
|
amap[k] = r.loneWord(ctx, elem.Value)
|
2018-01-03 19:12:40 +02:00
|
|
|
}
|
|
|
|
if !as.Append || !prevOk {
|
|
|
|
return amap
|
|
|
|
}
|
|
|
|
// TODO
|
|
|
|
return amap
|
|
|
|
}
|
|
|
|
// indexed array
|
|
|
|
maxIndex := len(elems) - 1
|
|
|
|
indexes := make([]int, len(elems))
|
|
|
|
for i, elem := range elems {
|
|
|
|
if elem.Index == nil {
|
|
|
|
indexes[i] = i
|
|
|
|
continue
|
|
|
|
}
|
2018-09-01 16:00:49 +02:00
|
|
|
k := r.arithm(ctx, elem.Index)
|
2018-01-03 19:12:40 +02:00
|
|
|
indexes[i] = k
|
|
|
|
if k > maxIndex {
|
|
|
|
maxIndex = k
|
|
|
|
}
|
|
|
|
}
|
|
|
|
strs := make([]string, maxIndex+1)
|
|
|
|
for i, elem := range elems {
|
2018-09-01 16:00:49 +02:00
|
|
|
strs[indexes[i]] = r.loneWord(ctx, elem.Value)
|
2018-01-03 19:12:40 +02:00
|
|
|
}
|
|
|
|
if !as.Append || !prevOk {
|
|
|
|
return IndexArray(strs)
|
|
|
|
}
|
|
|
|
switch x := prev.Value.(type) {
|
|
|
|
case StringVal:
|
|
|
|
prevList := IndexArray([]string{string(x)})
|
|
|
|
return append(prevList, strs...)
|
|
|
|
case IndexArray:
|
|
|
|
return append(x, strs...)
|
|
|
|
case AssocArray:
|
|
|
|
// TODO
|
|
|
|
}
|
|
|
|
return IndexArray(strs)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *Runner) ifsUpdated() {
|
|
|
|
runes := r.getVar("IFS")
|
|
|
|
r.ifsJoin = ""
|
|
|
|
if len(runes) > 0 {
|
|
|
|
r.ifsJoin = runes[:1]
|
|
|
|
}
|
|
|
|
r.ifsRune = func(r rune) bool {
|
|
|
|
for _, r2 := range runes {
|
|
|
|
if r == r2 {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
2018-01-21 13:39:15 +02:00
|
|
|
|
|
|
|
func (r *Runner) namesByPrefix(prefix string) []string {
|
|
|
|
var names []string
|
2018-04-28 20:35:15 +02:00
|
|
|
for _, name := range r.Env.Names() {
|
2018-01-21 13:39:15 +02:00
|
|
|
if strings.HasPrefix(name, prefix) {
|
|
|
|
names = append(names, name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for name := range r.Vars {
|
|
|
|
if strings.HasPrefix(name, prefix) {
|
|
|
|
names = append(names, name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return names
|
|
|
|
}
|