github.com/markusbkk/elvish@v0.0.0-20231204143114-91dc52438621/pkg/eval/vals/index_list.go (about)

     1  package vals
     2  
     3  import (
     4  	"errors"
     5  	"strconv"
     6  	"strings"
     7  
     8  	"github.com/markusbkk/elvish/pkg/eval/errs"
     9  )
    10  
    11  var (
    12  	errIndexMustBeInteger = errors.New("index must must be integer")
    13  )
    14  
    15  func 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.
    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 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
   105  func 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  
   139  func 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.
   151  func 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  
   165  func 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  
   171  func 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  }