1package vals 2 3import ( 4 "errors" 5 "strconv" 6 "strings" 7 8 "src.elv.sh/pkg/eval/errs" 9) 10 11var ( 12 errIndexMustBeInteger = errors.New("index must must be integer") 13) 14 15func 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. 29type ListIndex struct { 30 Slice bool 31 Lower int 32 Upper int 33} 34 35func 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. 56func 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", 88 ValidLow: strconv.Itoa(i - n), ValidHigh: "-1", 89 Actual: strconv.Itoa(j0)} 90 } 91 return nil, errs.OutOfRange{ 92 What: "slice upper index", 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 105func 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+1) 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+1) 127 if err != nil { 128 return false, 0, 0, err 129 } 130 if sep == "..=" { 131 // TODO: Handle j == MaxInt-1 132 j++ 133 } 134 } 135 // Two numbers 136 return true, i, j, nil 137} 138 139func splitIndexString(s string) (low, sep, high string) { 140 if i := strings.Index(s, "..="); i >= 0 { 141 return s[:i], "..=", s[i+3:] 142 } 143 if i := strings.Index(s, ".."); i >= 0 { 144 return s[:i], "..", s[i+2:] 145 } 146 return s, "", "" 147} 148 149// atoi is a wrapper around strconv.Atoi, converting strconv.ErrRange to 150// errs.OutOfRange. 151func atoi(a string, n int) (int, error) { 152 i, err := strconv.Atoi(a) 153 if err != nil { 154 if err.(*strconv.NumError).Err == strconv.ErrRange { 155 if i < 0 { 156 return 0, negIndexOutOfRange(a, n) 157 } 158 return 0, posIndexOutOfRange(a, n) 159 } 160 return 0, errIndexMustBeInteger 161 } 162 return i, nil 163} 164 165func posIndexOutOfRange(index string, n int) errs.OutOfRange { 166 return errs.OutOfRange{ 167 What: "index", 168 ValidLow: "0", ValidHigh: strconv.Itoa(n - 1), Actual: index} 169} 170 171func negIndexOutOfRange(index string, n int) errs.OutOfRange { 172 return errs.OutOfRange{ 173 What: "negative index", 174 ValidLow: strconv.Itoa(-n), ValidHigh: "-1", Actual: index} 175} 176