elvish/pkg/eval/vals/index_list.go
dunsany 9a26d6c645
Various has-key fixes (#1647)
* Correct slice indexing convention in code comment

A colon is not supported.

* Add more key slices tests

* Unify `has-key` tests with those from `pkg/eval/vals/has_key_test.go`

* Fix key slice format in documentation

Fixes #1646.

* Fix missing bracket

* Fix indexing

* Add more key slices `has-key` tests

* Fix `has-key` test expected value
2023-02-26 20:49:31 +00:00

181 lines
3.8 KiB
Go

package vals
import (
"errors"
"strconv"
"strings"
"src.elv.sh/pkg/eval/errs"
)
var (
errIndexMustBeInteger = errors.New("index 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
if j == -1 { // subtle corner case that is same as no high value
j = n
} else {
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}
}