123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175 |
- package vals
- import (
- "errors"
- "strconv"
- "strings"
- "src.elv.sh/pkg/eval/errs"
- )
- var (
- errIndexMustBeInteger = errors.New("index must must be integer")
- )
- func indexList(l List, rawIndex any) (any, error) {
- index, err := ConvertListIndex(rawIndex, l.Len())
- if err != nil {
- return nil, err
- }
- if index.Slice {
- return l.SubVector(index.Lower, index.Upper), nil
- }
- // Bounds are already checked.
- value, _ := l.Index(index.Lower)
- return value, nil
- }
- // ListIndex represents a (converted) list index.
- type ListIndex struct {
- Slice bool
- Lower int
- Upper int
- }
- func adjustAndCheckIndex(i, n int, includeN bool) (int, error) {
- if i < 0 {
- if i < -n {
- return 0, negIndexOutOfRange(strconv.Itoa(i), n)
- }
- return i + n, nil
- }
- if includeN {
- if i > n {
- return 0, posIndexOutOfRange(strconv.Itoa(i), n+1)
- }
- } else {
- if i >= n {
- return 0, posIndexOutOfRange(strconv.Itoa(i), n)
- }
- }
- return i, nil
- }
- // ConvertListIndex parses a list index, check whether it is valid, and returns
- // the converted structure.
- func ConvertListIndex(rawIndex any, n int) (*ListIndex, error) {
- switch rawIndex := rawIndex.(type) {
- case int:
- index, err := adjustAndCheckIndex(rawIndex, n, false)
- if err != nil {
- return nil, err
- }
- return &ListIndex{false, index, 0}, nil
- case string:
- slice, i, j, err := parseIndexString(rawIndex, n)
- if err != nil {
- return nil, err
- }
- if !slice {
- i, err = adjustAndCheckIndex(i, n, false)
- if err != nil {
- return nil, err
- }
- } else {
- i, err = adjustAndCheckIndex(i, n, true)
- if err != nil {
- return nil, err
- }
- j0 := j
- j, err = adjustAndCheckIndex(j, n, true)
- if err != nil {
- return nil, err
- }
- if j < i {
- if j0 < 0 {
- return nil, errs.OutOfRange{
- What: "negative slice upper index",
- ValidLow: strconv.Itoa(i - n), ValidHigh: "-1",
- Actual: strconv.Itoa(j0)}
- }
- return nil, errs.OutOfRange{
- What: "slice upper index",
- ValidLow: strconv.Itoa(i), ValidHigh: strconv.Itoa(n),
- Actual: strconv.Itoa(j0)}
- }
- }
- return &ListIndex{slice, i, j}, nil
- default:
- return nil, errIndexMustBeInteger
- }
- }
- // Index = Number |
- // Number ( ':' | '..' | '..=' ) Number
- func parseIndexString(s string, n int) (slice bool, i int, j int, err error) {
- low, sep, high := splitIndexString(s)
- if sep == "" {
- // A single number
- i, err := atoi(s, n)
- if err != nil {
- return false, 0, 0, err
- }
- return false, i, 0, nil
- }
- if low == "" {
- i = 0
- } else {
- i, err = atoi(low, n+1)
- if err != nil {
- return false, 0, 0, err
- }
- }
- if high == "" {
- j = n
- } else {
- j, err = atoi(high, n+1)
- if err != nil {
- return false, 0, 0, err
- }
- if sep == "..=" {
- // TODO: Handle j == MaxInt-1
- j++
- }
- }
- // Two numbers
- return true, i, j, nil
- }
- func splitIndexString(s string) (low, sep, high string) {
- if i := strings.Index(s, "..="); i >= 0 {
- return s[:i], "..=", s[i+3:]
- }
- if i := strings.Index(s, ".."); i >= 0 {
- return s[:i], "..", s[i+2:]
- }
- return s, "", ""
- }
- // atoi is a wrapper around strconv.Atoi, converting strconv.ErrRange to
- // errs.OutOfRange.
- func atoi(a string, n int) (int, error) {
- i, err := strconv.Atoi(a)
- if err != nil {
- if err.(*strconv.NumError).Err == strconv.ErrRange {
- if i < 0 {
- return 0, negIndexOutOfRange(a, n)
- }
- return 0, posIndexOutOfRange(a, n)
- }
- return 0, errIndexMustBeInteger
- }
- return i, nil
- }
- func posIndexOutOfRange(index string, n int) errs.OutOfRange {
- return errs.OutOfRange{
- What: "index",
- ValidLow: "0", ValidHigh: strconv.Itoa(n - 1), Actual: index}
- }
- func negIndexOutOfRange(index string, n int) errs.OutOfRange {
- return errs.OutOfRange{
- What: "negative index",
- ValidLow: strconv.Itoa(-n), ValidHigh: "-1", Actual: index}
- }
|