8e596d5979
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.
242 lines
6.5 KiB
Go
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)
|
|
}
|