Files
expr/operator-index.go
T

158 lines
4.6 KiB
Go

// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-index.go
package expr
import (
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
"git.portale-stac.it/go-pkg/expr/types"
"git.portale-stac.it/go-pkg/expr/types/array"
"git.portale-stac.it/go-pkg/expr/types/dict"
"git.portale-stac.it/go-pkg/expr/types/list"
)
// -------- index term
func newIndexTerm(tk *scan.Token) (inst *scan.Term) {
return &scan.Term{
Tk: *tk,
Children: make([]*scan.Term, 0, 2),
Position: scan.PosInfix,
Priority: scan.PriDot,
EvalFunc: evalIndex,
}
}
func verifyKey(indexList *array.ArrayType) (index any, err error) {
index = (*indexList)[0]
return
}
func verifyIndex(indexTerm *scan.Term, indexList *array.ArrayType, maxValue int64) (index int64, err error) {
var v int64
if v, err = types.ToGoInt64((*indexList)[0], "index expression"); err == nil {
if v < 0 && v >= -maxValue {
v = maxValue + v
}
if v >= 0 && v < maxValue {
index = v
} else {
err = indexTerm.Errorf("index %d out of bounds", v)
}
}
return
}
func verifyRange(indexTerm *scan.Term, indexList *array.ArrayType, maxValue int64) (startIndex, endIndex int64, err error) {
v, _ := ((*indexList)[0]).(*intPair)
startIndex = v.a
endIndex = v.b
if endIndex == kern.ConstLastIndex {
endIndex = int64(maxValue)
}
if startIndex < 0 && startIndex >= -maxValue {
startIndex = int64(maxValue) + startIndex
}
if endIndex < 0 && endIndex >= -maxValue {
endIndex = int64(maxValue) + endIndex
}
if startIndex < 0 || startIndex > maxValue {
err = indexTerm.Errorf("range start-index %d is out of bounds", startIndex)
} else if endIndex < 0 || endIndex > maxValue {
err = indexTerm.Errorf("range end-index %d is out of bounds", endIndex)
} else if startIndex > endIndex {
err = indexTerm.Errorf("range start-index %d must not be greater than end-index %d", startIndex, endIndex)
}
return
}
func evalIndex(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var leftValue, rightValue any
var indexList *array.ArrayType
var ok bool
if leftValue, rightValue, err = opTerm.EvalInfix(ctx); err != nil {
return
}
indexTerm := opTerm.Children[1]
if indexList, ok = rightValue.(*array.ArrayType); !ok {
err = opTerm.Errorf("invalid index expression")
return
} else if len(*indexList) != 1 {
err = indexTerm.Errorf("one index only is allowed")
return
}
if types.IsInteger((*indexList)[0]) {
switch unboxedValue := leftValue.(type) {
case *array.ArrayType:
var index int64
if index, err = verifyIndex(indexTerm, indexList, int64(len(*unboxedValue))); err == nil {
v = (*unboxedValue)[index]
}
case *list.LinkedList:
var index int64
if index, err = verifyIndex(indexTerm, indexList, int64(unboxedValue.Len())); err == nil {
v, err = unboxedValue.At(index)
}
case string:
var index int64
if index, err = verifyIndex(indexTerm, indexList, int64(len(unboxedValue))); err == nil {
v = string(unboxedValue[index])
}
case *dict.DictType:
v, err = getDictItem(unboxedValue, indexTerm, indexList, rightValue)
default:
err = opTerm.ErrIncompatibleTypes(leftValue, rightValue)
}
} else if isIntPair((*indexList)[0]) {
switch unboxedValue := leftValue.(type) {
case *array.ArrayType:
var start, end int64
if start, end, err = verifyRange(indexTerm, indexList, int64(len(*unboxedValue))); err == nil {
sublist := array.ArrayType((*unboxedValue)[start:end])
v = &sublist
}
case *list.LinkedList:
var start, end int64
if start, end, err = verifyRange(indexTerm, indexList, int64(unboxedValue.Len())); err == nil {
v = unboxedValue.Sub(start, end)
}
case string:
var start, end int64
if start, end, err = verifyRange(indexTerm, indexList, int64(len(unboxedValue))); err == nil {
v = unboxedValue[start:end]
}
default:
err = opTerm.ErrIncompatibleTypes(leftValue, rightValue)
}
} else if dict.IsDict(leftValue) {
d := leftValue.(*dict.DictType)
v, err = getDictItem(d, indexTerm, indexList, rightValue)
} else {
rightChild := opTerm.Children[1]
err = rightChild.Errorf("invalid index type: %v", (*indexList)[0])
}
return
}
func getDictItem(d *dict.DictType, indexTerm *scan.Term, indexList *array.ArrayType, rightValue any) (v any, err error) {
var ok bool
var indexValue any
if indexValue, err = verifyKey(indexList); err == nil {
if v, ok = (*d)[indexValue]; !ok {
err = indexTerm.Errorf("key %v does not belong to the dictionary", rightValue)
}
}
return
}
// init
func init() {
scan.RegisterTermConstructor(scan.SymIndex, newIndexTerm)
}