// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // All rights reserved. // data-cursors.go package expr import ( "io" "slices" ) type dataCursor struct { ds map[string]Functor ctx ExprContext initState bool // true if no item has produced yet (this replace di initial Next() call in the contructor) // cursorValid bool // true if resource is nil or if clean has not yet been called 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, // cursorValid: true, index: -1, count: 0, current: nil, lastErr: nil, resource: resource, ctx: ctx.Clone(), nextFunc: ds[NextName], cleanFunc: ds[CleanName], resetFunc: ds[ResetName], } 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 = slices.Contains([]string{CleanName, ResetName, CurrentName, IndexName}, name) 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 name == CleanName { err = dc.Clean() } else if name == ResetName { err = dc.Reset() } else if functor, ok := dc.ds[name]; ok && isFunctor(functor) { ctx := cloneContext(dc.ctx) value, err = functor.InvokeNamed(ctx, name, args) exportObjects(dc.ctx, ctx) } else { err = errNoOperation(name) } return } // func (dc *dataCursor) Reset() (err error) { // if dc.resetFunc != nil { // if dc.resource != nil { // ctx := cloneContext(dc.ctx) // actualParams := bindActualParams(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 // } else { // err = errInvalidDataSource() // } // } else { // err = errNoOperation(ResetName) // } // return // } func (dc *dataCursor) Reset() (err error) { if dc.resetFunc != nil { ctx := cloneContext(dc.ctx) actualParams := bindActualParams(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 return } func (dc *dataCursor) Clean() (err error) { if dc.cleanFunc != nil { ctx := cloneContext(dc.ctx) actualParams := bindActualParams(dc.cleanFunc, []any{dc.resource}) _, err = dc.cleanFunc.InvokeNamed(ctx, CleanName, actualParams) exportObjects(dc.ctx, ctx) } dc.lastErr = io.EOF return } // func (dc *dataCursor) Clean() (err error) { // if dc.cleanFunc != nil { // if dc.resource != nil { // ctx := cloneContext(dc.ctx) // actualParams := bindActualParams(dc.cleanFunc, []any{dc.resource}) // _, err = dc.cleanFunc.InvokeNamed(ctx, CleanName, actualParams) // exportObjects(dc.ctx, ctx) // } else { // err = errInvalidDataSource() // } // } else { // err = errNoOperation(CleanName) // } // 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 := bindActualParams(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 := bindActualParams(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 filter := dc.ds[FilterName] mapper := dc.ds[MapName] var item any for item == nil && dc.lastErr == nil { ctx := cloneContext(dc.ctx) dc.index++ actualParams := bindActualParams(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() } return } // 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 := bindActualParams(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 }