From aa66d07caaa0648c017abc64aefeadb5a8d77d64 Mon Sep 17 00:00:00 2001 From: Celestino Amoroso Date: Sun, 28 Apr 2024 06:45:20 +0200 Subject: [PATCH] Iterators now support generic operation interface based on methods HasOperation() and CallOperation(). Also fixed mul() function that called doAdd() instead of doMul(). --- data-cursor.go | 23 +++++++++++++-- func-math.go | 7 ++++- iter-list.go | 8 ++++++ iterator.go | 8 ++++++ operand-iterator.go | 70 ++++++++++++++++++++++++++++++++++++--------- 5 files changed, 100 insertions(+), 16 deletions(-) diff --git a/data-cursor.go b/data-cursor.go index 4f6814d..5190eed 100644 --- a/data-cursor.go +++ b/data-cursor.go @@ -18,7 +18,7 @@ const ( ) type dataCursor struct { - ds map[any]*term + ds map[string]Functor ctx ExprContext index int resource any @@ -28,8 +28,9 @@ type dataCursor struct { currentFunc Functor } -func newDataCursor(ctx ExprContext) (dc *dataCursor) { +func newDataCursor(ctx ExprContext, ds map[string]Functor) (dc *dataCursor) { dc = &dataCursor{ + ds: ds, index: -1, ctx: ctx.Clone(), } @@ -40,6 +41,24 @@ func (dc *dataCursor) String() string { return "$(...)" } +func (dc *dataCursor) HasOperation(name string) (exists bool) { + f, ok := dc.ds[name] + exists = ok && isFunctor(f) + return +} + +func (dc *dataCursor) CallOperation(name string) (value any, err error) { + if functor, ok := dc.ds[name]; ok && isFunctor(functor) { + ctx := cloneContext(dc.ctx) + value, err = functor.Invoke(ctx, name, []any{}) + exportObjects(dc.ctx, ctx) + + } else { + err = errNoOperation(name) + } + return +} + func (dc *dataCursor) Reset() (err error) { if dc.resetFunc != nil { ctx := cloneContext(dc.ctx) diff --git a/func-math.go b/func-math.go index 869f2a5..0a301ac 100644 --- a/func-math.go +++ b/func-math.go @@ -27,6 +27,11 @@ func doAdd(ctx ExprContext, name string, it Iterator) (result any, err error) { if v, err = doAdd(ctx, name, subIter); err != nil { break } + if subIter.HasOperation(cleanName) { + if _, err = subIter.CallOperation(cleanName, nil); err != nil { + return + } + } } else { if err = checkNumberParamExpected(name, v, it.Index()); err != nil { break @@ -77,7 +82,7 @@ func doMul(ctx ExprContext, name string, it Iterator) (result any, err error) { } if array, ok := v.([]any); ok { - if v, err = doAdd(ctx, name, NewFlatArrayIterator(array)); err != nil { + if v, err = doMul(ctx, name, NewFlatArrayIterator(array)); err != nil { break } } diff --git a/iter-list.go b/iter-list.go index 3fd3cb1..37ccf89 100644 --- a/iter-list.go +++ b/iter-list.go @@ -15,6 +15,14 @@ func NewFlatArrayIterator(array []any) *FlatArrayIterator { return &FlatArrayIterator{a: array, index: 0} } +func (it *FlatArrayIterator) HasOperation(name string) bool { + return false +} + +func (it *FlatArrayIterator) CallOperation(name string, args []any) (any, error) { + return nil, errNoOperation(name) +} + func (it *FlatArrayIterator) Current() (item any, err error) { if it.index >= 0 && it.index < len(it.a) { item = it.a[it.index] diff --git a/iterator.go b/iterator.go index 90144b8..4d750d3 100644 --- a/iterator.go +++ b/iterator.go @@ -4,8 +4,16 @@ // iterator.go package expr +import "fmt" + type Iterator interface { Next() (item any, err error) // must return io.EOF after the last item Current() (item any, err error) Index() int + HasOperation(name string) bool + CallOperation(name string, args []any) (value any, err error) +} + +func errNoOperation(name string) error { + return fmt.Errorf("no %q function defined in the data-source", name) } diff --git a/operand-iterator.go b/operand-iterator.go index 0bad7ad..3ed0013 100644 --- a/operand-iterator.go +++ b/operand-iterator.go @@ -6,6 +6,8 @@ package expr import ( "fmt" + "slices" + "strings" ) // -------- iterator term @@ -41,6 +43,44 @@ func evalTermArray(ctx ExprContext, a []*term) (values []any, err error) { return } +// func getDataSourceDict(ctx ExprContext, self *term) (ds map[string]Functor, err error) { +// var value any +// if len(self.children) < 1 || self.children[0] == nil { +// err = self.Errorf("missing the data-source parameter") +// return +// } + +// if value, err = self.children[0].compute(ctx); err != nil { +// return +// } + +// if dictAny, ok := value.(map[any]any); ok { +// ds = make(map[string]Functor) +// // required functions +// for _, k := range []string{currentName, nextName} { +// if item, exists := dictAny[k]; exists && item != nil { +// if functor, ok := item.(*funcDefFunctor); ok { +// ds[k] = functor +// } +// } else { +// err = fmt.Errorf("the data-source must provide a non-nil %q operator", k) +// return +// } +// } +// // Optional functions +// for _, k := range []string{initName, resetName, cleanName} { +// if item, exists := dictAny[k]; exists && item != nil { +// if functor, ok := item.(*funcDefFunctor); ok { +// ds[k] = functor +// } +// } +// } +// } else { +// err = self.Errorf("the first param (data-source) of an iterator must be a dict, not a %T", value) +// } +// return +// } + func getDataSourceDict(ctx ExprContext, self *term) (ds map[string]Functor, err error) { var value any if len(self.children) < 1 || self.children[0] == nil { @@ -52,26 +92,30 @@ func getDataSourceDict(ctx ExprContext, self *term) (ds map[string]Functor, err return } + requiredFields := []string{currentName, nextName} + fieldsMask := 0b11 + foundFields := 0 if dictAny, ok := value.(map[any]any); ok { ds = make(map[string]Functor) - // required functions - for _, k := range []string{currentName, nextName} { - if item, exists := dictAny[k]; exists && item != nil { + for keyAny, item := range dictAny { + if key, ok := keyAny.(string); ok { if functor, ok := item.(*funcDefFunctor); ok { - ds[k] = functor + ds[key] = functor + if index := slices.Index(requiredFields, key); index >= 0 { + foundFields |= 1 << index + } } - } else { - err = fmt.Errorf("the data-source must provide a non-nil %q operator", k) - return } } - // Optional functions - for _, k := range []string{initName, resetName, cleanName} { - if item, exists := dictAny[k]; exists && item != nil { - if functor, ok := item.(*funcDefFunctor); ok { - ds[k] = functor + // check required functions + if foundFields != fieldsMask { + missingFields := make([]string, 0, len(requiredFields)) + for index, field := range requiredFields { + if (foundFields & (1 << index)) == 0 { + missingFields = append(missingFields, field) } } + err = fmt.Errorf("the data-source must provide a non-nil %q operator(s)", strings.Join(missingFields, ", ")) } } else { err = self.Errorf("the first param (data-source) of an iterator must be a dict, not a %T", value) @@ -86,7 +130,7 @@ func evalIterator(ctx ExprContext, self *term) (v any, err error) { return } - dc := newDataCursor(ctx) + dc := newDataCursor(ctx, ds) if initFunc, exists := ds[initName]; exists && initFunc != nil { var args []any