Iterator: added support for iterators over dictionaries
This commit is contained in:
parent
d7dd628fc9
commit
807df0f3a8
209
dict-iterator.go
Normal file
209
dict-iterator.go
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// dict-iterator.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type dictIterMode int
|
||||||
|
|
||||||
|
const (
|
||||||
|
dictIterModeKeys dictIterMode = iota
|
||||||
|
dictIterModeValues
|
||||||
|
dictIterModeItems
|
||||||
|
)
|
||||||
|
|
||||||
|
type DictIterator struct {
|
||||||
|
a *DictType
|
||||||
|
count int
|
||||||
|
index int
|
||||||
|
keys []any
|
||||||
|
iterMode dictIterMode
|
||||||
|
}
|
||||||
|
|
||||||
|
type sortType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
sortTypeNone sortType = iota
|
||||||
|
sortTypeAsc
|
||||||
|
sortTypeDesc
|
||||||
|
sortTypeDefault = sortTypeAsc
|
||||||
|
)
|
||||||
|
|
||||||
|
func (it *DictIterator) makeKeys(m map[any]any, sort sortType) {
|
||||||
|
it.keys = make([]any, 0, len(m))
|
||||||
|
|
||||||
|
if sort == sortTypeNone {
|
||||||
|
for keyAny := range m {
|
||||||
|
it.keys = append(it.keys, keyAny)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
scalarMap := make(map[string]any, len(m))
|
||||||
|
scalerKeys := make([]string, 0, len(m))
|
||||||
|
for keyAny := range m {
|
||||||
|
keyStr := fmt.Sprint(keyAny)
|
||||||
|
scalarMap[keyStr] = keyAny
|
||||||
|
scalerKeys = append(scalerKeys, keyStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch sort {
|
||||||
|
case sortTypeAsc:
|
||||||
|
slices.Sort(scalerKeys)
|
||||||
|
case sortTypeDesc:
|
||||||
|
slices.Sort(scalerKeys)
|
||||||
|
slices.Reverse(scalerKeys)
|
||||||
|
}
|
||||||
|
for _, keyStr := range scalerKeys {
|
||||||
|
it.keys = append(it.keys, scalarMap[keyStr])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDictIterator(dict *DictType, args []any) (it *DictIterator, err error) {
|
||||||
|
var sortType = sortTypeNone
|
||||||
|
var s string
|
||||||
|
it = &DictIterator{a: dict, count: 0, index: -1, keys: nil, iterMode: dictIterModeKeys}
|
||||||
|
if len(args) > 0 {
|
||||||
|
if s, err = ToGoString(args[0], "sort type"); err == nil {
|
||||||
|
switch strings.ToLower(s) {
|
||||||
|
case "a", "asc":
|
||||||
|
sortType = sortTypeAsc
|
||||||
|
case "d", "desc":
|
||||||
|
sortType = sortTypeDesc
|
||||||
|
case "n", "none", "nosort", "no-sort":
|
||||||
|
sortType = sortTypeNone
|
||||||
|
case "", "default":
|
||||||
|
sortType = sortTypeDefault
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("invalid sort type %q", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil && len(args) > 1 {
|
||||||
|
if s, err = ToGoString(args[1], "iteration mode"); err == nil {
|
||||||
|
switch strings.ToLower(s) {
|
||||||
|
case "k", "key", "keys":
|
||||||
|
it.iterMode = dictIterModeKeys
|
||||||
|
case "v", "value", "values":
|
||||||
|
it.iterMode = dictIterModeValues
|
||||||
|
case "i", "item", "items":
|
||||||
|
it.iterMode = dictIterModeItems
|
||||||
|
case "", "default":
|
||||||
|
it.iterMode = dictIterModeKeys
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("invalid iteration mode %q", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
it.makeKeys(*dict, sortType)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMapIterator(m map[any]any) (it *DictIterator) {
|
||||||
|
it = &DictIterator{a: (*DictType)(&m), count: 0, index: -1, keys: nil}
|
||||||
|
it.makeKeys(m, sortTypeNone)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *DictIterator) String() string {
|
||||||
|
var l = 0
|
||||||
|
if it.a != nil {
|
||||||
|
l = len(*it.a)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("$(#%d)", l)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *DictIterator) TypeName() string {
|
||||||
|
return "DictIterator"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *DictIterator) HasOperation(name string) bool {
|
||||||
|
// yes := name == NextName || name == ResetName || name == IndexName || name == CountName || name == CurrentName
|
||||||
|
yes := slices.Contains([]string{NextName, ResetName, IndexName, CountName, CurrentName, CleanName, KeyName, ValueName}, name)
|
||||||
|
return yes
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *DictIterator) CallOperation(name string, args map[string]any) (v any, err error) {
|
||||||
|
switch name {
|
||||||
|
case NextName:
|
||||||
|
v, err = it.Next()
|
||||||
|
case ResetName:
|
||||||
|
err = it.Reset()
|
||||||
|
case CleanName:
|
||||||
|
err = it.Clean()
|
||||||
|
case IndexName:
|
||||||
|
v = int64(it.Index())
|
||||||
|
case CurrentName:
|
||||||
|
v, err = it.Current()
|
||||||
|
case CountName:
|
||||||
|
v = it.count
|
||||||
|
case KeyName:
|
||||||
|
if it.index >= 0 && it.index < len(it.keys) {
|
||||||
|
v = it.keys[it.index]
|
||||||
|
} else {
|
||||||
|
err = io.EOF
|
||||||
|
}
|
||||||
|
case ValueName:
|
||||||
|
if it.index >= 0 && it.index < len(it.keys) {
|
||||||
|
a := *(it.a)
|
||||||
|
v = a[it.keys[it.index]]
|
||||||
|
} else {
|
||||||
|
err = io.EOF
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = errNoOperation(name)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *DictIterator) Current() (item any, err error) {
|
||||||
|
if it.index >= 0 && it.index < len(it.keys) {
|
||||||
|
switch it.iterMode {
|
||||||
|
case dictIterModeKeys:
|
||||||
|
item = it.keys[it.index]
|
||||||
|
case dictIterModeValues:
|
||||||
|
a := *(it.a)
|
||||||
|
item = a[it.keys[it.index]]
|
||||||
|
case dictIterModeItems:
|
||||||
|
a := *(it.a)
|
||||||
|
pair := []any{it.keys[it.index], a[it.keys[it.index]]}
|
||||||
|
item = newList(pair)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = io.EOF
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *DictIterator) Next() (item any, err error) {
|
||||||
|
it.index++
|
||||||
|
if item, err = it.Current(); err != io.EOF {
|
||||||
|
it.count++
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *DictIterator) Index() int {
|
||||||
|
return it.index
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *DictIterator) Count() int {
|
||||||
|
return it.count
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *DictIterator) Reset() error {
|
||||||
|
it.index = -1
|
||||||
|
it.count = 0
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *DictIterator) Clean() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@ -21,6 +21,8 @@ const (
|
|||||||
CountName = "count"
|
CountName = "count"
|
||||||
FilterName = "filter"
|
FilterName = "filter"
|
||||||
MapName = "map"
|
MapName = "map"
|
||||||
|
KeyName = "key"
|
||||||
|
ValueName = "value"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Iterator interface {
|
type Iterator interface {
|
||||||
@ -29,14 +31,14 @@ type Iterator interface {
|
|||||||
Current() (item any, err error)
|
Current() (item any, err error)
|
||||||
Index() int
|
Index() int
|
||||||
Count() int
|
Count() int
|
||||||
|
HasOperation(name string) bool
|
||||||
|
CallOperation(name string, args map[string]any) (value any, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ExtIterator interface {
|
type ExtIterator interface {
|
||||||
Iterator
|
Iterator
|
||||||
Reset() error
|
Reset() error
|
||||||
Clean() error
|
Clean() error
|
||||||
HasOperation(name string) bool
|
|
||||||
CallOperation(name string, args map[string]any) (value any, err error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func errNoOperation(name string) error {
|
func errNoOperation(name string) error {
|
||||||
|
|||||||
@ -149,12 +149,12 @@ func (it *ListIterator) Count() int {
|
|||||||
return it.count
|
return it.count
|
||||||
}
|
}
|
||||||
|
|
||||||
func (it *ListIterator) Reset() (error) {
|
func (it *ListIterator) Reset() error {
|
||||||
it.index = it.start - it.step
|
it.index = it.start - it.step
|
||||||
it.count = 0
|
it.count = 0
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (it *ListIterator) Clean() (error) {
|
func (it *ListIterator) Clean() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -86,37 +86,49 @@ func evalIterator(ctx ExprContext, opTerm *term) (v any, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if ds, err = getDataSourceDict(opTerm, firstChildValue); err != nil {
|
if ds, err = getDataSourceDict(opTerm, firstChildValue); err != nil && ds == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
err = nil
|
||||||
|
|
||||||
if ds != nil {
|
if ds != nil {
|
||||||
var dc *dataCursor
|
if len(ds) > 0 {
|
||||||
dcCtx := ctx.Clone()
|
var dc *dataCursor
|
||||||
if initFunc, exists := ds[InitName]; exists && initFunc != nil {
|
dcCtx := ctx.Clone()
|
||||||
var args []any
|
if initFunc, exists := ds[InitName]; exists && initFunc != nil {
|
||||||
var resource any
|
var args []any
|
||||||
if len(opTerm.children) > 1 {
|
var resource any
|
||||||
if args, err = evalTermArray(ctx, opTerm.children[1:]); err != nil {
|
if len(opTerm.children) > 1 {
|
||||||
|
if args, err = evalTermArray(ctx, opTerm.children[1:]); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
args = []any{}
|
||||||
|
}
|
||||||
|
|
||||||
|
actualParams := bindActualParams(initFunc, args)
|
||||||
|
|
||||||
|
initCtx := ctx.Clone()
|
||||||
|
if resource, err = initFunc.InvokeNamed(initCtx, InitName, actualParams); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
exportObjects(dcCtx, initCtx)
|
||||||
|
dc = NewDataCursor(dcCtx, ds, resource)
|
||||||
} else {
|
} else {
|
||||||
args = []any{}
|
dc = NewDataCursor(dcCtx, ds, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
actualParams := bindActualParams(initFunc, args)
|
v = dc
|
||||||
|
|
||||||
initCtx := ctx.Clone()
|
|
||||||
if resource, err = initFunc.InvokeNamed(initCtx, InitName, actualParams); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
exportObjects(dcCtx, initCtx)
|
|
||||||
dc = NewDataCursor(dcCtx, ds, resource)
|
|
||||||
} else {
|
} else {
|
||||||
dc = NewDataCursor(dcCtx, ds, nil)
|
if dictIt, ok := firstChildValue.(*DictType); ok {
|
||||||
|
var args []any
|
||||||
|
if args, err = evalSibling(ctx, opTerm.children, nil); err == nil {
|
||||||
|
v, err = NewDictIterator(dictIt, args)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = opTerm.children[0].Errorf("the data-source must be a dictionary")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
v = dc
|
|
||||||
} else if list, ok := firstChildValue.(*ListType); ok {
|
} else if list, ok := firstChildValue.(*ListType); ok {
|
||||||
var args []any
|
var args []any
|
||||||
if args, err = evalSibling(ctx, opTerm.children, nil); err == nil {
|
if args, err = evalSibling(ctx, opTerm.children, nil); err == nil {
|
||||||
|
|||||||
@ -24,7 +24,27 @@ func evalPostInc(ctx ExprContext, opTerm *term) (v any, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if it, ok := childValue.(Iterator); ok {
|
if it, ok := childValue.(Iterator); ok {
|
||||||
|
var namePrefix string
|
||||||
v, err = it.Next()
|
v, err = it.Next()
|
||||||
|
|
||||||
|
if opTerm.children[0].symbol() == SymVariable {
|
||||||
|
namePrefix = opTerm.children[0].source()
|
||||||
|
}
|
||||||
|
ctx.UnsafeSetVar(namePrefix+"_index", it.Index())
|
||||||
|
if c, err1 := it.Current(); err1 == nil {
|
||||||
|
ctx.UnsafeSetVar(namePrefix+"_current", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
if it.HasOperation(KeyName) {
|
||||||
|
if k, err1 := it.CallOperation(KeyName, nil); err1 == nil {
|
||||||
|
ctx.UnsafeSetVar(namePrefix+"_key", k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if it.HasOperation(ValueName) {
|
||||||
|
if v1, err1 := it.CallOperation(ValueName, nil); err1 == nil {
|
||||||
|
ctx.UnsafeSetVar(namePrefix+"_value", v1)
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if IsInteger(childValue) && opTerm.children[0].symbol() == SymVariable {
|
} else if IsInteger(childValue) && opTerm.children[0].symbol() == SymVariable {
|
||||||
v = childValue
|
v = childValue
|
||||||
i, _ := childValue.(int64)
|
i, _ := childValue.(int64)
|
||||||
|
|||||||
@ -27,8 +27,10 @@ func TestIteratorParser(t *testing.T) {
|
|||||||
/* 16 */ {`include "test-resources/filter.expr"; it=$(ds,10); it++`, int64(2), nil},
|
/* 16 */ {`include "test-resources/filter.expr"; it=$(ds,10); it++`, int64(2), nil},
|
||||||
/* 17 */ {`it=$({"next":func(){5}}); it++`, int64(5), nil},
|
/* 17 */ {`it=$({"next":func(){5}}); it++`, int64(5), nil},
|
||||||
/* 18 */ {`it=$({"next":func(){5}}); it.clean`, nil, nil},
|
/* 18 */ {`it=$({"next":func(){5}}); it.clean`, nil, nil},
|
||||||
|
/* 19 */ {`it=$({1:"one",2:"two",3:"three"}); it++`, int64(1), nil},
|
||||||
|
/* 20 */ {`it=$({1:"one",2:"two",3:"three"}, "default", "value"); it++`, "one", nil},
|
||||||
}
|
}
|
||||||
|
|
||||||
//runTestSuiteSpec(t, section, inputs, 18)
|
runTestSuiteSpec(t, section, inputs, 20)
|
||||||
runTestSuite(t, section, inputs)
|
// runTestSuite(t, section, inputs)
|
||||||
}
|
}
|
||||||
|
|||||||
9
utils.go
9
utils.go
@ -218,6 +218,15 @@ func ToGoInt(value any, description string) (i int, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ToGoString(value any, description string) (s string, err error) {
|
||||||
|
if s, ok := value.(string); ok {
|
||||||
|
return s, nil
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("%s expected string, got %s (%v)", description, TypeName(value), value)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func ForAll[T, V any](ts []T, fn func(T) V) []V {
|
func ForAll[T, V any](ts []T, fn func(T) V) []V {
|
||||||
result := make([]V, len(ts))
|
result := make([]V, len(ts))
|
||||||
for i, t := range ts {
|
for i, t := range ts {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user