src.elv.sh@v0.21.0-dev.0.20240515223629-06979efb9a2a/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 be integer") 13 ) 14 15 func indexList(l List, rawIndex any) (any, 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 any, 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 // 105 // Number ( '..' | '..=' ) Number 106 func parseIndexString(s string, n int) (slice bool, i int, j int, err error) { 107 low, sep, high := splitIndexString(s) 108 if sep == "" { 109 // A single number 110 i, err := atoi(s, n) 111 if err != nil { 112 return false, 0, 0, err 113 } 114 return false, i, 0, nil 115 } 116 if low == "" { 117 i = 0 118 } else { 119 i, err = atoi(low, n+1) 120 if err != nil { 121 return false, 0, 0, err 122 } 123 } 124 if high == "" { 125 j = n 126 } else { 127 j, err = atoi(high, n+1) 128 if err != nil { 129 return false, 0, 0, err 130 } 131 if sep == "..=" { 132 // TODO: Handle j == MaxInt-1 133 if j == -1 { // subtle corner case that is same as no high value 134 j = n 135 } else { 136 j++ 137 } 138 } 139 } 140 // Two numbers 141 return true, i, j, nil 142 } 143 144 func splitIndexString(s string) (low, sep, high string) { 145 if i := strings.Index(s, "..="); i >= 0 { 146 return s[:i], "..=", s[i+3:] 147 } 148 if i := strings.Index(s, ".."); i >= 0 { 149 return s[:i], "..", s[i+2:] 150 } 151 return s, "", "" 152 } 153 154 // atoi is a wrapper around strconv.Atoi, converting strconv.ErrRange to 155 // errs.OutOfRange. 156 func atoi(a string, n int) (int, error) { 157 i, err := strconv.Atoi(a) 158 if err != nil { 159 if err.(*strconv.NumError).Err == strconv.ErrRange { 160 if i < 0 { 161 return 0, negIndexOutOfRange(a, n) 162 } 163 return 0, posIndexOutOfRange(a, n) 164 } 165 return 0, errIndexMustBeInteger 166 } 167 return i, nil 168 } 169 170 func posIndexOutOfRange(index string, n int) errs.OutOfRange { 171 return errs.OutOfRange{ 172 What: "index", 173 ValidLow: "0", ValidHigh: strconv.Itoa(n - 1), Actual: index} 174 } 175 176 func negIndexOutOfRange(index string, n int) errs.OutOfRange { 177 return errs.OutOfRange{ 178 What: "negative index", 179 ValidLow: strconv.Itoa(-n), ValidHigh: "-1", Actual: index} 180 }