// 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) }