// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com). // All rights reserved. // operator-index.go package expr import ( "strings" "git.portale-stac.it/go-pkg/expr/kern" "git.portale-stac.it/go-pkg/expr/scan" "git.portale-stac.it/go-pkg/expr/sym" "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/interval" "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 kern.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 verifyInterval(indexTerm kern.Term, indexList *array.ArrayType, maxValue int64) (startIndex, endIndex int64, err error) { // v, _ := ((*indexList)[0]).(*interval.IntervalType) // startIndex = v.Begin() // endIndex = v.End() // 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("%s start-index %d is out of bounds", interval.TypeName, startIndex) // } else if endIndex < 0 || endIndex > maxValue { // err = indexTerm.Errorf("%s end-index %d is out of bounds", interval.TypeName, endIndex) // } else if startIndex > endIndex { // err = indexTerm.Errorf("%s start-index %d must not be greater than end-index %d", interval.TypeName, 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]) { v, err = collectionItemByIndex(opTerm, leftValue, rightValue, indexList) } else if interval.IsInterval((*indexList)[0]) { v, err = subCollectionByIndex(opTerm, leftValue, rightValue, indexList) } 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 collectionItemByIndex(opTerm kern.Term, leftValue, rightValue any, indexList *array.ArrayType) (v any, err error) { indexTerm := opTerm.GetChild(1) 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) } return } func subCollectionByIndex(opTerm kern.Term, leftValue, rightValue any, indexList *array.ArrayType) (v any, err error) { triple := (*indexList)[0].(*interval.IntervalType) switch unboxedValue := leftValue.(type) { case *array.ArrayType: v = intervalSubArray(unboxedValue, triple) case *list.LinkedList: v = intervalSubLinkedList(unboxedValue, triple) case string: v = intervalSubString(unboxedValue, triple) default: err = opTerm.ErrIncompatibleTypes(leftValue, rightValue) } return } func fixInterval(p *interval.IntervalType, maxLength int) (b, e, s int) { b, e, s = p.ToIntTriple() if b < 0 { b = maxLength + b } if e < 0 { e = maxLength + e //+ 1 } return } func intervalSubString(source string, p *interval.IntervalType) (result string) { b, e, s := fixInterval(p, len(source)) if b <= e { e = min(e, len(source)) if s == 1 { result = source[b:e] } else { var sb strings.Builder for i := b; i < e; i += s { sb.WriteByte(source[i]) } result = sb.String() } } else { var sb strings.Builder for i := b; i >= e; i -= s { sb.WriteByte(source[i]) } result = sb.String() } return } func intervalSubArray(source *array.ArrayType, p *interval.IntervalType) (result *array.ArrayType) { var a array.ArrayType b, e, s := fixInterval(p, len(*source)) if b <= e { e = min(e, len(*source)) if s == 1 { a = (*source)[b:e] } else { size := (e-b)/s + 1 a = make(array.ArrayType, 0, size) for i := b; i < e; i += s { a = append(a, (*source)[i]) } } } else { size := (b-e)/s + 1 a = make(array.ArrayType, 0, size) for i := b; i > e; i -= s { a = append(a, (*source)[i]) } } result = &a return } func intervalSubLinkedList(source *list.LinkedList, p *interval.IntervalType) (result *list.LinkedList) { b, e, s := fixInterval(p, source.Len()) revert := b > e if revert { b, e = e, b source = source.Revert() } e = min(e, source.Len()) if s == 1 { result = source.Sub(int64(b), int64(e)) } else { result = source.SubStep(int64(b), int64(e), int64(s)) } if revert { source = source.Revert() } return } func getDictItem(d *dict.DictType, indexTerm kern.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(sym.SymIndex, newIndexTerm) }