diff --git a/dict-iterator.go b/dict-iterator.go new file mode 100644 index 0000000..2e1c4a1 --- /dev/null +++ b/dict-iterator.go @@ -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 +} diff --git a/iterator.go b/iterator.go index b5112a4..cc33c86 100644 --- a/iterator.go +++ b/iterator.go @@ -21,6 +21,8 @@ const ( CountName = "count" FilterName = "filter" MapName = "map" + KeyName = "key" + ValueName = "value" ) type Iterator interface { @@ -29,14 +31,14 @@ type Iterator interface { Current() (item any, err error) Index() int Count() int + HasOperation(name string) bool + CallOperation(name string, args map[string]any) (value any, err error) } type ExtIterator interface { Iterator Reset() error Clean() error - HasOperation(name string) bool - CallOperation(name string, args map[string]any) (value any, err error) } func errNoOperation(name string) error { diff --git a/list-iterator.go b/list-iterator.go index 4165bd3..01e36fd 100644 --- a/list-iterator.go +++ b/list-iterator.go @@ -149,12 +149,12 @@ func (it *ListIterator) Count() int { return it.count } -func (it *ListIterator) Reset() (error) { +func (it *ListIterator) Reset() error { it.index = it.start - it.step it.count = 0 return nil } -func (it *ListIterator) Clean() (error) { +func (it *ListIterator) Clean() error { return nil } diff --git a/operand-iterator.go b/operand-iterator.go index 0128dbe..6766130 100644 --- a/operand-iterator.go +++ b/operand-iterator.go @@ -86,37 +86,49 @@ func evalIterator(ctx ExprContext, opTerm *term) (v any, err error) { return } - if ds, err = getDataSourceDict(opTerm, firstChildValue); err != nil { + if ds, err = getDataSourceDict(opTerm, firstChildValue); err != nil && ds == nil { return } + err = nil if ds != nil { - var dc *dataCursor - dcCtx := ctx.Clone() - if initFunc, exists := ds[InitName]; exists && initFunc != nil { - var args []any - var resource any - if len(opTerm.children) > 1 { - if args, err = evalTermArray(ctx, opTerm.children[1:]); err != nil { + if len(ds) > 0 { + var dc *dataCursor + dcCtx := ctx.Clone() + if initFunc, exists := ds[InitName]; exists && initFunc != nil { + var args []any + var resource any + 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 } + exportObjects(dcCtx, initCtx) + dc = NewDataCursor(dcCtx, ds, resource) } else { - args = []any{} + dc = NewDataCursor(dcCtx, ds, nil) } - actualParams := bindActualParams(initFunc, args) - - initCtx := ctx.Clone() - if resource, err = initFunc.InvokeNamed(initCtx, InitName, actualParams); err != nil { - return - } - exportObjects(dcCtx, initCtx) - dc = NewDataCursor(dcCtx, ds, resource) + v = dc } 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 { var args []any if args, err = evalSibling(ctx, opTerm.children, nil); err == nil { diff --git a/operator-post-inc-dec.go b/operator-post-inc-dec.go index 8b79c4d..3216a62 100644 --- a/operator-post-inc-dec.go +++ b/operator-post-inc-dec.go @@ -24,7 +24,27 @@ func evalPostInc(ctx ExprContext, opTerm *term) (v any, err error) { } if it, ok := childValue.(Iterator); ok { + var namePrefix string 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 { v = childValue i, _ := childValue.(int64) diff --git a/t_iterator_test.go b/t_iterator_test.go index 01dcb76..9552306 100644 --- a/t_iterator_test.go +++ b/t_iterator_test.go @@ -27,8 +27,10 @@ func TestIteratorParser(t *testing.T) { /* 16 */ {`include "test-resources/filter.expr"; it=$(ds,10); it++`, int64(2), nil}, /* 17 */ {`it=$({"next":func(){5}}); it++`, int64(5), 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) - runTestSuite(t, section, inputs) + runTestSuiteSpec(t, section, inputs, 20) + // runTestSuite(t, section, inputs) } diff --git a/utils.go b/utils.go index 2d236ca..03a0fda 100644 --- a/utils.go +++ b/utils.go @@ -218,6 +218,15 @@ func ToGoInt(value any, description string) (i int, err error) { 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 { result := make([]V, len(ts)) for i, t := range ts {