2024-04-26 08:01:35 +02:00
|
|
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
|
|
|
// All rights reserved.
|
|
|
|
|
|
|
|
// data-cursors.go
|
|
|
|
package expr
|
|
|
|
|
|
|
|
import (
|
|
|
|
"io"
|
|
|
|
)
|
|
|
|
|
|
|
|
type dataCursor struct {
|
2024-07-21 16:34:52 +02:00
|
|
|
ds map[string]Functor
|
|
|
|
ctx ExprContext
|
2024-07-23 05:46:37 +02:00
|
|
|
initState bool // true if no item has prodiced yet (this replace di initial Next() call in the contructor)
|
2024-07-21 16:34:52 +02:00
|
|
|
index int
|
|
|
|
count int
|
|
|
|
current any
|
|
|
|
lastErr error
|
|
|
|
resource any
|
|
|
|
nextFunc Functor
|
|
|
|
cleanFunc Functor
|
|
|
|
resetFunc Functor
|
2024-04-26 08:01:35 +02:00
|
|
|
}
|
|
|
|
|
2024-07-21 05:45:22 +02:00
|
|
|
func NewDataCursor(ctx ExprContext, ds map[string]Functor, resource any) (dc *dataCursor) {
|
2024-04-26 21:03:22 +02:00
|
|
|
dc = &dataCursor{
|
2024-07-23 05:46:37 +02:00
|
|
|
ds: ds,
|
|
|
|
initState: true,
|
|
|
|
index: -1,
|
|
|
|
count: 0,
|
|
|
|
current: nil,
|
|
|
|
lastErr: nil,
|
|
|
|
resource: resource,
|
|
|
|
ctx: ctx.Clone(),
|
|
|
|
nextFunc: ds[NextName],
|
2024-07-21 16:34:52 +02:00
|
|
|
cleanFunc: ds[CleanName],
|
|
|
|
resetFunc: ds[ResetName],
|
2024-04-26 21:03:22 +02:00
|
|
|
}
|
2024-07-23 05:46:37 +02:00
|
|
|
//dc.Next()
|
2024-04-26 21:03:22 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-07-18 07:27:02 +02:00
|
|
|
func (dc *dataCursor) Context() ExprContext {
|
|
|
|
return dc.ctx
|
|
|
|
}
|
|
|
|
|
2024-07-06 16:05:54 +02:00
|
|
|
func (dc *dataCursor) TypeName() string {
|
|
|
|
return "DataCursor"
|
|
|
|
}
|
|
|
|
|
2024-05-04 00:57:21 +02:00
|
|
|
// func mapToString(m map[string]Functor) string {
|
|
|
|
// var sb strings.Builder
|
|
|
|
// sb.WriteByte('{')
|
|
|
|
// for key, _ := range m {
|
|
|
|
// if sb.Len() > 1 {
|
|
|
|
// sb.WriteString(fmt.Sprintf(", %q: func(){}", key))
|
|
|
|
// } else {
|
|
|
|
// sb.WriteString(fmt.Sprintf("%q: func(){}", key))
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// sb.WriteByte('}')
|
|
|
|
// return sb.String()
|
|
|
|
// }
|
2024-05-01 05:59:54 +02:00
|
|
|
|
2024-04-26 08:01:35 +02:00
|
|
|
func (dc *dataCursor) String() string {
|
2024-05-01 05:59:54 +02:00
|
|
|
return "$()"
|
2024-05-04 00:37:31 +02:00
|
|
|
/*
|
|
|
|
var sb strings.Builder
|
|
|
|
sb.WriteString(fmt.Sprintf(`$(
|
|
|
|
index: %d,
|
|
|
|
ds: %s,
|
|
|
|
ctx: `, dc.index, mapToString(dc.ds)))
|
|
|
|
CtxToBuilder(&sb, dc.ctx, 1)
|
|
|
|
sb.WriteByte(')')
|
|
|
|
return sb.String()
|
|
|
|
*/
|
2024-04-26 08:01:35 +02:00
|
|
|
}
|
|
|
|
|
2024-04-28 06:45:20 +02:00
|
|
|
func (dc *dataCursor) HasOperation(name string) (exists bool) {
|
2024-07-18 07:27:02 +02:00
|
|
|
exists = name == IndexName
|
2024-05-04 01:24:13 +02:00
|
|
|
if !exists {
|
|
|
|
f, ok := dc.ds[name]
|
|
|
|
exists = ok && isFunctor(f)
|
|
|
|
}
|
2024-04-28 06:45:20 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-07-31 09:08:58 +02:00
|
|
|
func (dc *dataCursor) CallOperation(name string, args map[string]any) (value any, err error) {
|
2024-07-18 07:27:02 +02:00
|
|
|
if name == IndexName {
|
2024-05-04 08:07:49 +02:00
|
|
|
value = int64(dc.Index())
|
2024-05-04 01:24:13 +02:00
|
|
|
} else if functor, ok := dc.ds[name]; ok && isFunctor(functor) {
|
2024-05-01 05:59:54 +02:00
|
|
|
if functor == dc.cleanFunc {
|
2024-05-04 01:24:13 +02:00
|
|
|
value, err = dc.Clean()
|
2024-05-01 05:59:54 +02:00
|
|
|
} else if functor == dc.resetFunc {
|
2024-05-04 01:24:13 +02:00
|
|
|
value, err = dc.Reset()
|
2024-05-01 05:59:54 +02:00
|
|
|
} else {
|
|
|
|
ctx := cloneContext(dc.ctx)
|
2024-07-31 09:08:58 +02:00
|
|
|
value, err = functor.InvokeNamed(ctx, name, args)
|
2024-05-01 05:59:54 +02:00
|
|
|
exportObjects(dc.ctx, ctx)
|
|
|
|
}
|
2024-04-28 06:45:20 +02:00
|
|
|
} else {
|
|
|
|
err = errNoOperation(name)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-05-04 00:37:31 +02:00
|
|
|
func (dc *dataCursor) Reset() (success bool, err error) {
|
2024-04-27 22:31:14 +02:00
|
|
|
if dc.resetFunc != nil {
|
2024-05-01 05:59:54 +02:00
|
|
|
if dc.resource != nil {
|
|
|
|
ctx := cloneContext(dc.ctx)
|
2024-07-28 18:49:08 +02:00
|
|
|
actualParams := buildActualParams(dc.resetFunc, []any{dc.resource})
|
|
|
|
_, err = dc.resetFunc.InvokeNamed(ctx, ResetName, actualParams)
|
2024-05-01 05:59:54 +02:00
|
|
|
exportObjects(dc.ctx, ctx)
|
2024-07-23 05:46:37 +02:00
|
|
|
dc.index = -1
|
|
|
|
dc.count = 0
|
|
|
|
dc.initState = true
|
|
|
|
dc.current = nil
|
|
|
|
dc.lastErr = nil
|
|
|
|
//dc.Next()
|
2024-05-01 05:59:54 +02:00
|
|
|
} else {
|
|
|
|
err = errInvalidDataSource()
|
2024-04-27 22:31:14 +02:00
|
|
|
}
|
2024-04-28 04:41:43 +02:00
|
|
|
} else {
|
2024-07-18 07:27:02 +02:00
|
|
|
err = errNoOperation(ResetName)
|
2024-04-27 22:31:14 +02:00
|
|
|
}
|
2024-05-04 00:37:31 +02:00
|
|
|
success = err == nil
|
2024-04-27 22:31:14 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-05-04 00:37:31 +02:00
|
|
|
func (dc *dataCursor) Clean() (success bool, err error) {
|
2024-04-28 04:41:43 +02:00
|
|
|
if dc.cleanFunc != nil {
|
2024-05-01 05:59:54 +02:00
|
|
|
if dc.resource != nil {
|
|
|
|
ctx := cloneContext(dc.ctx)
|
2024-07-28 18:49:08 +02:00
|
|
|
actualParams := buildActualParams(dc.cleanFunc, []any{dc.resource})
|
|
|
|
_, err = dc.cleanFunc.InvokeNamed(ctx, CleanName, actualParams)
|
2024-07-23 05:46:37 +02:00
|
|
|
// dc.resource = nil
|
2024-05-01 05:59:54 +02:00
|
|
|
exportObjects(dc.ctx, ctx)
|
|
|
|
} else {
|
|
|
|
err = errInvalidDataSource()
|
|
|
|
}
|
2024-04-28 04:41:43 +02:00
|
|
|
} else {
|
2024-07-23 05:46:37 +02:00
|
|
|
err = errNoOperation(CleanName)
|
2024-04-27 22:31:14 +02:00
|
|
|
}
|
2024-05-04 00:37:31 +02:00
|
|
|
success = err == nil
|
2024-04-27 22:31:14 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-04-26 08:01:35 +02:00
|
|
|
func (dc *dataCursor) Current() (item any, err error) { // must return io.EOF at the last item
|
2024-07-23 05:46:37 +02:00
|
|
|
dc.init()
|
|
|
|
|
2024-07-21 05:45:22 +02:00
|
|
|
if dc.current != nil {
|
|
|
|
item = dc.current
|
|
|
|
} else {
|
2024-04-26 08:01:35 +02:00
|
|
|
err = io.EOF
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-07-13 06:44:00 +02:00
|
|
|
func (dc *dataCursor) checkFilter(filter Functor, item any) (accepted bool, err error) {
|
|
|
|
var v any
|
|
|
|
var ok bool
|
|
|
|
ctx := cloneContext(dc.ctx)
|
2024-07-28 18:49:08 +02:00
|
|
|
|
|
|
|
actualParams := buildActualParams(filter, []any{item, dc.index})
|
|
|
|
if v, err = filter.InvokeNamed(ctx, FilterName, actualParams); err == nil && v != nil {
|
2024-07-13 06:44:00 +02:00
|
|
|
if accepted, ok = v.(bool); !ok {
|
2024-07-21 05:45:22 +02:00
|
|
|
accepted = true // NOTE: A non-boolean value that is not nil means the item has been accepted
|
|
|
|
}
|
2024-04-26 08:01:35 +02:00
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
2024-07-21 05:45:22 +02:00
|
|
|
|
2024-07-13 06:44:00 +02:00
|
|
|
func (dc *dataCursor) mapItem(mapper Functor, item any) (mappedItem any, err error) {
|
|
|
|
ctx := cloneContext(dc.ctx)
|
2024-07-28 18:49:08 +02:00
|
|
|
actualParams := buildActualParams(mapper, []any{item, dc.index})
|
|
|
|
mappedItem, err = mapper.InvokeNamed(ctx, MapName, actualParams)
|
2024-07-11 06:53:14 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-07-23 05:46:37 +02:00
|
|
|
func (dc *dataCursor) init() {
|
|
|
|
if dc.initState {
|
|
|
|
dc.initState = false
|
|
|
|
dc.Next()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-21 05:45:22 +02:00
|
|
|
func (dc *dataCursor) Next() (current any, err error) { // must return io.EOF after the last item
|
2024-07-23 05:46:37 +02:00
|
|
|
if dc.initState {
|
|
|
|
dc.init()
|
|
|
|
} else if err = dc.lastErr; err != nil {
|
2024-07-21 05:45:22 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
current = dc.current
|
2024-07-11 06:53:14 +02:00
|
|
|
if dc.resource != nil {
|
2024-07-18 07:27:02 +02:00
|
|
|
filter := dc.ds[FilterName]
|
|
|
|
mapper := dc.ds[MapName]
|
2024-07-21 05:45:22 +02:00
|
|
|
var item any
|
|
|
|
for item == nil && dc.lastErr == nil {
|
2024-07-13 00:12:08 +02:00
|
|
|
ctx := cloneContext(dc.ctx)
|
2024-07-21 05:45:22 +02:00
|
|
|
dc.index++
|
2024-07-28 18:49:08 +02:00
|
|
|
|
|
|
|
actualParams := buildActualParams(dc.nextFunc, []any{dc.resource, dc.index})
|
|
|
|
if item, dc.lastErr = dc.nextFunc.InvokeNamed(ctx, NextName, actualParams); dc.lastErr == nil {
|
2024-07-11 06:53:14 +02:00
|
|
|
if item == nil {
|
2024-07-21 05:45:22 +02:00
|
|
|
dc.lastErr = io.EOF
|
2024-07-11 06:53:14 +02:00
|
|
|
} else {
|
2024-07-21 16:34:52 +02:00
|
|
|
accepted := true
|
2024-07-13 06:44:00 +02:00
|
|
|
if filter != nil {
|
2024-07-21 05:45:22 +02:00
|
|
|
if accepted, dc.lastErr = dc.checkFilter(filter, item); dc.lastErr != nil || !accepted {
|
2024-07-13 06:44:00 +02:00
|
|
|
item = nil
|
|
|
|
}
|
|
|
|
}
|
2024-07-21 16:34:52 +02:00
|
|
|
if accepted {
|
|
|
|
dc.count++
|
|
|
|
}
|
2024-07-13 06:44:00 +02:00
|
|
|
if item != nil && mapper != nil {
|
2024-07-21 05:45:22 +02:00
|
|
|
item, dc.lastErr = dc.mapItem(mapper, item)
|
2024-07-13 06:44:00 +02:00
|
|
|
}
|
2024-07-11 06:53:14 +02:00
|
|
|
}
|
|
|
|
}
|
2024-07-13 00:12:08 +02:00
|
|
|
exportObjects(dc.ctx, ctx)
|
2024-07-11 06:53:14 +02:00
|
|
|
}
|
2024-07-21 05:45:22 +02:00
|
|
|
dc.current = item
|
|
|
|
if dc.lastErr != nil {
|
2024-07-21 16:34:52 +02:00
|
|
|
dc.index--
|
2024-07-21 05:45:22 +02:00
|
|
|
dc.Clean()
|
|
|
|
}
|
2024-07-11 06:53:14 +02:00
|
|
|
} else {
|
2024-07-21 05:45:22 +02:00
|
|
|
dc.lastErr = errInvalidDataSource()
|
2024-07-11 06:53:14 +02:00
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-04-26 08:01:35 +02:00
|
|
|
func (dc *dataCursor) Index() int {
|
2024-07-21 16:34:52 +02:00
|
|
|
return dc.index - 1
|
|
|
|
}
|
|
|
|
|
|
|
|
func (dc *dataCursor) Count() int {
|
|
|
|
return dc.count
|
2024-04-26 08:01:35 +02:00
|
|
|
}
|
2024-07-28 18:49:08 +02:00
|
|
|
|