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  }