github.com/elves/elvish@v0.15.0/pkg/eval/vals/index_list.go (about)

     1  package vals
     2  
     3  import (
     4  	"errors"
     5  	"strconv"
     6  	"strings"
     7  
     8  	"github.com/elves/elvish/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 float64:
    59  		index := int(rawIndex)
    60  		if rawIndex != float64(index) {
    61  			return nil, errIndexMustBeInteger
    62  		}
    63  		index, err := adjustAndCheckIndex(index, n, false)
    64  		if err != nil {
    65  			return nil, err
    66  		}
    67  		return &ListIndex{false, index, 0}, nil
    68  	case string:
    69  		slice, i, j, err := parseIndexString(rawIndex, n)
    70  		if err != nil {
    71  			return nil, err
    72  		}
    73  		if !slice {
    74  			i, err = adjustAndCheckIndex(i, n, false)
    75  			if err != nil {
    76  				return nil, err
    77  			}
    78  		} else {
    79  			i, err = adjustAndCheckIndex(i, n, true)
    80  			if err != nil {
    81  				return nil, err
    82  			}
    83  			j0 := j
    84  			j, err = adjustAndCheckIndex(j, n, true)
    85  			if err != nil {
    86  				return nil, err
    87  			}
    88  			if j < i {
    89  				if j0 < 0 {
    90  					return nil, errs.OutOfRange{
    91  						What:     "negative slice upper index here",
    92  						ValidLow: strconv.Itoa(i - n), ValidHigh: "-1",
    93  						Actual: strconv.Itoa(j0)}
    94  				}
    95  				return nil, errs.OutOfRange{
    96  					What:     "slice upper index here",
    97  					ValidLow: strconv.Itoa(i), ValidHigh: strconv.Itoa(n),
    98  					Actual: strconv.Itoa(j0)}
    99  			}
   100  		}
   101  		return &ListIndex{slice, i, j}, nil
   102  	default:
   103  		return nil, errIndexMustBeInteger
   104  	}
   105  }
   106  
   107  // Index = Number |
   108  //         Number ( ':' | '..' | '..=' ) Number
   109  func parseIndexString(s string, n int) (slice bool, i int, j int, err error) {
   110  	low, sep, high := splitIndexString(s)
   111  	if sep == "" {
   112  		// A single number
   113  		i, err := atoi(s, n)
   114  		if err != nil {
   115  			return false, 0, 0, err
   116  		}
   117  		return false, i, 0, nil
   118  	}
   119  	if low == "" {
   120  		i = 0
   121  	} else {
   122  		i, err = atoi(low, n)
   123  		if err != nil {
   124  			return false, 0, 0, err
   125  		}
   126  	}
   127  	if high == "" {
   128  		j = n
   129  	} else {
   130  		j, err = atoi(high, n)
   131  		if err != nil {
   132  			return false, 0, 0, err
   133  		}
   134  		if sep == "..=" {
   135  			j++
   136  		}
   137  	}
   138  	// Two numbers
   139  	return true, i, j, nil
   140  }
   141  
   142  func splitIndexString(s string) (low, sep, high string) {
   143  	if i := strings.IndexRune(s, ':'); i >= 0 {
   144  		return s[:i], ":", s[i+1:]
   145  	}
   146  	if i := strings.Index(s, "..="); i >= 0 {
   147  		return s[:i], "..=", s[i+3:]
   148  	}
   149  	if i := strings.Index(s, ".."); i >= 0 {
   150  		return s[:i], "..", s[i+2:]
   151  	}
   152  	return s, "", ""
   153  }
   154  
   155  // atoi is a wrapper around strconv.Atoi, converting strconv.ErrRange to
   156  // errs.OutOfRange.
   157  func atoi(a string, n int) (int, error) {
   158  	i, err := strconv.Atoi(a)
   159  	if err != nil {
   160  		if err.(*strconv.NumError).Err == strconv.ErrRange {
   161  			if i < 0 {
   162  				return 0, negIndexOutOfRange(a, n)
   163  			}
   164  			return 0, posIndexOutOfRange(a, n)
   165  		}
   166  		return 0, errIndexMustBeInteger
   167  	}
   168  	return i, nil
   169  }
   170  
   171  func posIndexOutOfRange(index string, n int) errs.OutOfRange {
   172  	return errs.OutOfRange{
   173  		What:     "index here",
   174  		ValidLow: "0", ValidHigh: strconv.Itoa(n - 1), Actual: index}
   175  }
   176  
   177  func negIndexOutOfRange(index string, n int) errs.OutOfRange {
   178  	return errs.OutOfRange{
   179  		What:     "negative index here",
   180  		ValidLow: strconv.Itoa(-n), ValidHigh: "-1", Actual: index}
   181  }