// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // All rights reserved. // data-cursors.go package expr import ( "io" ) type dataCursor struct { ds map[string]Functor ctx ExprContext initState bool // true if no item has prodiced yet (this replace di initial Next() call in the contructor) 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) { dc = &dataCursor{ ds: ds, initState: true, index: -1, count: 0, current: nil, lastErr: nil, resource: resource, ctx: ctx.Clone(), nextFunc: ds[NextName], cleanFunc: ds[CleanName], resetFunc: ds[ResetName], } //dc.Next() return } func (dc *dataCursor) Context() ExprContext { return dc.ctx } func (dc *dataCursor) TypeName() string { return "DataCursor" } // 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 map[string]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, args) 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) 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 if dc.resource != nil { filter := dc.ds[FilterName] mapper := dc.ds[MapName] var item any for item == nil && dc.lastErr == nil { 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 { if item == nil { dc.lastErr = io.EOF } else { accepted := true if filter != nil { if accepted, dc.lastErr = dc.checkFilter(filter, item); dc.lastErr != nil || !accepted { item = nil } } if accepted { dc.count++ } if item != nil && mapper != nil { item, dc.lastErr = dc.mapItem(mapper, item) } } } exportObjects(dc.ctx, ctx) } dc.current = item if dc.lastErr != nil { dc.index-- dc.Clean() } } else { dc.lastErr = errInvalidDataSource() } return } func (dc *dataCursor) Index() int { return dc.index - 1 } func (dc *dataCursor) Count() int { return dc.count }