src.elv.sh@v0.21.0-dev.0.20240515223629-06979efb9a2a/pkg/eval/vals/index_list.go (about)

     1  package vals
     2  
     3  import (
     4  	"errors"
     5  	"strconv"
     6  	"strings"
     7  
     8  	"src.elv.sh/pkg/eval/errs"
     9  )
    10  
    11  var (
    12  	errIndexMustBeInteger = errors.New("index must be integer")
    13  )
    14  
    15  func indexList(l List, rawIndex any) (any, 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 any, 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  //
   105  //	Number ( '..' | '..=' ) Number
   106  func parseIndexString(s string, n int) (slice bool, i int, j int, err error) {
   107  	low, sep, high := splitIndexString(s)
   108  	if sep == "" {
   109  		// A single number
   110  		i, err := atoi(s, n)
   111  		if err != nil {
   112  			return false, 0, 0, err
   113  		}
   114  		return false, i, 0, nil
   115  	}
   116  	if low == "" {
   117  		i = 0
   118  	} else {
   119  		i, err = atoi(low, n+1)
   120  		if err != nil {
   121  			return false, 0, 0, err
   122  		}
   123  	}
   124  	if high == "" {
   125  		j = n
   126  	} else {
   127  		j, err = atoi(high, n+1)
   128  		if err != nil {
   129  			return false, 0, 0, err
   130  		}
   131  		if sep == "..=" {
   132  			// TODO: Handle j == MaxInt-1
   133  			if j == -1 { // subtle corner case that is same as no high value
   134  				j = n
   135  			} else {
   136  				j++
   137  			}
   138  		}
   139  	}
   140  	// Two numbers
   141  	return true, i, j, nil
   142  }
   143  
   144  func splitIndexString(s string) (low, sep, high string) {
   145  	if i := strings.Index(s, "..="); i >= 0 {
   146  		return s[:i], "..=", s[i+3:]
   147  	}
   148  	if i := strings.Index(s, ".."); i >= 0 {
   149  		return s[:i], "..", s[i+2:]
   150  	}
   151  	return s, "", ""
   152  }
   153  
   154  // atoi is a wrapper around strconv.Atoi, converting strconv.ErrRange to
   155  // errs.OutOfRange.
   156  func atoi(a string, n int) (int, error) {
   157  	i, err := strconv.Atoi(a)
   158  	if err != nil {
   159  		if err.(*strconv.NumError).Err == strconv.ErrRange {
   160  			if i < 0 {
   161  				return 0, negIndexOutOfRange(a, n)
   162  			}
   163  			return 0, posIndexOutOfRange(a, n)
   164  		}
   165  		return 0, errIndexMustBeInteger
   166  	}
   167  	return i, nil
   168  }
   169  
   170  func posIndexOutOfRange(index string, n int) errs.OutOfRange {
   171  	return errs.OutOfRange{
   172  		What:     "index",
   173  		ValidLow: "0", ValidHigh: strconv.Itoa(n - 1), Actual: index}
   174  }
   175  
   176  func negIndexOutOfRange(index string, n int) errs.OutOfRange {
   177  	return errs.OutOfRange{
   178  		What:     "negative index",
   179  		ValidLow: strconv.Itoa(-n), ValidHigh: "-1", Actual: index}
   180  }