expr/data-cursor.go

255 lines
5.7 KiB
Go
Raw Normal View History

// 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
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
}
func NewDataCursor(ctx ExprContext, ds map[string]Functor, resource any) (dc *dataCursor) {
2024-04-26 21:03:22 +02:00
dc = &dataCursor{
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
}
//dc.Next()
2024-04-26 21:03:22 +02:00
return
}
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()
// }
func (dc *dataCursor) String() string {
return "$()"
/*
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()
*/
}
func (dc *dataCursor) HasOperation(name string) (exists bool) {
exists = name == IndexName
if !exists {
f, ok := dc.ds[name]
exists = ok && isFunctor(f)
}
return
}
func (dc *dataCursor) CallOperation(name string, args []any) (value any, err error) {
if name == IndexName {
value = int64(dc.Index())
} else if functor, ok := dc.ds[name]; ok && isFunctor(functor) {
if functor == dc.cleanFunc {
value, err = dc.Clean()
} else if functor == dc.resetFunc {
value, err = dc.Reset()
} else {
ctx := cloneContext(dc.ctx)
value, err = functor.InvokeNamed(ctx, name, map[string]any{})
exportObjects(dc.ctx, ctx)
}
} else {
err = errNoOperation(name)
}
return
}
func (dc *dataCursor) Reset() (success bool, err error) {
if dc.resetFunc != nil {
if dc.resource != nil {
ctx := cloneContext(dc.ctx)
actualParams := buildActualParams(dc.resetFunc, []any{dc.resource})
_, err = dc.resetFunc.InvokeNamed(ctx, ResetName, actualParams)
exportObjects(dc.ctx, ctx)
dc.index = -1
dc.count = 0
dc.initState = true
dc.current = nil
dc.lastErr = nil
//dc.Next()
} else {
err = errInvalidDataSource()
}
} else {
err = errNoOperation(ResetName)
}
success = err == nil
return
}
func (dc *dataCursor) Clean() (success bool, err error) {
if dc.cleanFunc != nil {
if dc.resource != nil {
ctx := cloneContext(dc.ctx)
actualParams := buildActualParams(dc.cleanFunc, []any{dc.resource})
_, err = dc.cleanFunc.InvokeNamed(ctx, CleanName, actualParams)
// dc.resource = nil
exportObjects(dc.ctx, ctx)
} else {
err = errInvalidDataSource()
}
} else {
err = errNoOperation(CleanName)
}
success = err == nil
return
}
func (dc *dataCursor) Current() (item any, err error) { // must return io.EOF at the last item
dc.init()
if dc.current != nil {
item = dc.current
} else {
err = io.EOF
}
return
}
func (dc *dataCursor) checkFilter(filter Functor, item any) (accepted bool, err error) {
var v any
var ok bool
ctx := cloneContext(dc.ctx)
actualParams := buildActualParams(filter, []any{item, dc.index})
if v, err = filter.InvokeNamed(ctx, FilterName, actualParams); err == nil && v != nil {
if accepted, ok = v.(bool); !ok {
accepted = true // NOTE: A non-boolean value that is not nil means the item has been accepted
}
}
return
}
func (dc *dataCursor) mapItem(mapper Functor, item any) (mappedItem any, err error) {
ctx := cloneContext(dc.ctx)
actualParams := buildActualParams(mapper, []any{item, dc.index})
mappedItem, err = mapper.InvokeNamed(ctx, MapName, actualParams)
2024-07-11 06:53:14 +02:00
return
}
func (dc *dataCursor) init() {
if dc.initState {
dc.initState = false
dc.Next()
}
}
func (dc *dataCursor) Next() (current any, err error) { // must return io.EOF after the last item
if dc.initState {
dc.init()
} else if err = dc.lastErr; err != nil {
return
}
current = dc.current
2024-07-11 06:53:14 +02:00
if dc.resource != nil {
filter := dc.ds[FilterName]
mapper := dc.ds[MapName]
var item any
for item == nil && dc.lastErr == nil {
2024-07-13 00:12:08 +02:00
ctx := cloneContext(dc.ctx)
dc.index++
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 {
dc.lastErr = io.EOF
2024-07-11 06:53:14 +02:00
} else {
2024-07-21 16:34:52 +02:00
accepted := true
if filter != nil {
if accepted, dc.lastErr = dc.checkFilter(filter, item); dc.lastErr != nil || !accepted {
item = nil
}
}
2024-07-21 16:34:52 +02:00
if accepted {
dc.count++
}
if item != nil && mapper != nil {
item, dc.lastErr = dc.mapItem(mapper, item)
}
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
}
dc.current = item
if dc.lastErr != nil {
2024-07-21 16:34:52 +02:00
dc.index--
dc.Clean()
}
2024-07-11 06:53:14 +02:00
} else {
dc.lastErr = errInvalidDataSource()
2024-07-11 06:53:14 +02:00
}
return
}
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
}
func buildActualParams(functor Functor, args []any) (actualParams map[string]any) {
formalParams := functor.GetParams()
actualParams = make(map[string]any, len(formalParams))
for i, spec := range formalParams {
if i < len(formalParams) {
actualParams[spec.Name()] = args[i]
} else {
actualParams[spec.Name()] = nil
}
}
return
}