mirror of
https://github.com/go-sylixos/elvish.git
synced 2024-12-04 10:57:50 +08:00
9a26d6c645
* 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
181 lines
3.8 KiB
Go
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}
|
|
}
|