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