Files
expr/operator-index.go
T
camoroso 8e596d5979 several changes:
1. moved scan/symbol.go to new package sym and adapted all files that reference Symbol type and its values.
2. new type interval defined by begin, end and step values.
3. replaced operator-range.go with operator-interval.go.
4. replaced range operator begin:end with begin..end..step.
5. new implementation of sub-collection extraction based on new interval literal.
2026-07-27 16:01:52 +02:00

242 lines
6.5 KiB
Go

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