github.com/kolbycrouch/elvish@v0.14.1-0.20210614162631-215b9ac1c423/pkg/eval/vals/index_list.go (about)

     1  package vals
     2  
     3  import (
     4  	"errors"
     5  	"strconv"
     6  	"strings"
     7  
     8  	"src.elv.sh/pkg/eval/errs"
     9  )
    10  
    11  var (
    12  	errIndexMustBeInteger = errors.New("index must must be integer")
    13  )
    14  
    15  func indexList(l List, rawIndex interface{}) (interface{}, error) {
    16  	index, err := ConvertListIndex(rawIndex, l.Len())
    17  	if err != nil {
    18  		return nil, err
    19  	}
    20  	if index.Slice {
    21  		return l.SubVector(index.Lower, index.Upper), nil
    22  	}
    23  	// Bounds are already checked.
    24  	value, _ := l.Index(index.Lower)
    25  	return value, nil
    26  }
    27  
    28  // ListIndex represents a (converted) list index.
    29  type ListIndex struct {
    30  	Slice bool
    31  	Lower int
    32  	Upper int
    33  }
    34  
    35  func adjustAndCheckIndex(i, n int, includeN bool) (int, error) {
    36  	if i < 0 {
    37  		if i < -n {
    38  			return 0, negIndexOutOfRange(strconv.Itoa(i), n)
    39  		}
    40  		return i + n, nil
    41  	}
    42  	if includeN {
    43  		if i > n {
    44  			return 0, posIndexOutOfRange(strconv.Itoa(i), n+1)
    45  		}
    46  	} else {
    47  		if i >= n {
    48  			return 0, posIndexOutOfRange(strconv.Itoa(i), n)
    49  		}
    50  	}
    51  	return i, nil
    52  }
    53  
    54  // ConvertListIndex parses a list index, check whether it is valid, and returns
    55  // the converted structure.
    56  func ConvertListIndex(rawIndex interface{}, n int) (*ListIndex, error) {
    57  	switch rawIndex := rawIndex.(type) {
    58  	case int:
    59  		index, err := adjustAndCheckIndex(rawIndex, n, false)
    60  		if err != nil {
    61  			return nil, err
    62  		}
    63  		return &ListIndex{false, index, 0}, nil
    64  	case string:
    65  		slice, i, j, err := parseIndexString(rawIndex, n)
    66  		if err != nil {
    67  			return nil, err
    68  		}
    69  		if !slice {
    70  			i, err = adjustAndCheckIndex(i, n, false)
    71  			if err != nil {
    72  				return nil, err
    73  			}
    74  		} else {
    75  			i, err = adjustAndCheckIndex(i, n, true)
    76  			if err != nil {
    77  				return nil, err
    78  			}
    79  			j0 := j
    80  			j, err = adjustAndCheckIndex(j, n, true)
    81  			if err != nil {
    82  				return nil, err
    83  			}
    84  			if j < i {
    85  				if j0 < 0 {
    86  					return nil, errs.OutOfRange{
    87  						What:     "negative slice upper index here",
    88  						ValidLow: strconv.Itoa(i - n), ValidHigh: "-1",
    89  						Actual: strconv.Itoa(j0)}
    90  				}
    91  				return nil, errs.OutOfRange{
    92  					What:     "slice upper index here",
    93  					ValidLow: strconv.Itoa(i), ValidHigh: strconv.Itoa(n),
    94  					Actual: strconv.Itoa(j0)}
    95  			}
    96  		}
    97  		return &ListIndex{slice, i, j}, nil
    98  	default:
    99  		return nil, errIndexMustBeInteger
   100  	}
   101  }
   102  
   103  // Index = Number |
   104  //         Number ( ':' | '..' | '..=' ) Number
   105  func parseIndexString(s string, n int) (slice bool, i int, j int, err error) {
   106  	low, sep, high := splitIndexString(s)
   107  	if sep == "" {
   108  		// A single number
   109  		i, err := atoi(s, n)
   110  		if err != nil {
   111  			return false, 0, 0, err
   112  		}
   113  		return false, i, 0, nil
   114  	}
   115  	if low == "" {
   116  		i = 0
   117  	} else {
   118  		i, err = atoi(low, n)
   119  		if err != nil {
   120  			return false, 0, 0, err
   121  		}
   122  	}
   123  	if high == "" {
   124  		j = n
   125  	} else {
   126  		j, err = atoi(high, n)
   127  		if err != nil {
   128  			return false, 0, 0, err
   129  		}
   130  		if sep == "..=" {
   131  			j++
   132  		}
   133  	}
   134  	// Two numbers
   135  	return true, i, j, nil
   136  }
   137  
   138  func splitIndexString(s string) (low, sep, high string) {
   139  	if i := strings.IndexRune(s, ':'); i >= 0 {
   140  		return s[:i], ":", s[i+1:]
   141  	}
   142  	if i := strings.Index(s, "..="); i >= 0 {
   143  		return s[:i], "..=", s[i+3:]
   144  	}
   145  	if i := strings.Index(s, ".."); i >= 0 {
   146  		return s[:i], "..", s[i+2:]
   147  	}
   148  	return s, "", ""
   149  }
   150  
   151  // atoi is a wrapper around strconv.Atoi, converting strconv.ErrRange to
   152  // errs.OutOfRange.
   153  func atoi(a string, n int) (int, error) {
   154  	i, err := strconv.Atoi(a)
   155  	if err != nil {
   156  		if err.(*strconv.NumError).Err == strconv.ErrRange {
   157  			if i < 0 {
   158  				return 0, negIndexOutOfRange(a, n)
   159  			}
   160  			return 0, posIndexOutOfRange(a, n)
   161  		}
   162  		return 0, errIndexMustBeInteger
   163  	}
   164  	return i, nil
   165  }
   166  
   167  func posIndexOutOfRange(index string, n int) errs.OutOfRange {
   168  	return errs.OutOfRange{
   169  		What:     "index here",
   170  		ValidLow: "0", ValidHigh: strconv.Itoa(n - 1), Actual: index}
   171  }
   172  
   173  func negIndexOutOfRange(index string, n int) errs.OutOfRange {
   174  	return errs.OutOfRange{
   175  		What:     "negative index here",
   176  		ValidLow: strconv.Itoa(-n), ValidHigh: "-1", Actual: index}
   177  }