// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // All rights reserved. // data-cursors.go package expr import ( "errors" "io" ) type dataCursor struct { ds map[string]Functor ctx ExprContext index int resource any nextFunc Functor cleanFunc Functor resetFunc Functor currentFunc Functor } func newDataCursor(ctx ExprContext, ds map[string]Functor) (dc *dataCursor) { dc = &dataCursor{ ds: ds, index: -1, ctx: ctx.Clone(), } return } 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 []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.Invoke(ctx, name, []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) if _, err = dc.resetFunc.Invoke(ctx, resetName, []any{dc.resource}); err == nil { dc.index = -1 } exportObjects(dc.ctx, ctx) } 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) _, err = dc.cleanFunc.Invoke(ctx, cleanName, []any{dc.resource}) dc.resource = nil exportObjects(dc.ctx, ctx) } else { err = errInvalidDataSource() } } else { err = errors.New("no 'clean' function defined in the data-source") } success = err == nil return } func (dc *dataCursor) Current() (item any, err error) { // must return io.EOF at the last item ctx := cloneContext(dc.ctx) if item, err = dc.currentFunc.Invoke(ctx, currentName, []any{}); err == nil && item == nil { err = io.EOF } exportObjects(dc.ctx, ctx) return } // func (dc *dataCursor) _Next() (item any, err error) { // must return io.EOF after the last item // if dc.resource != nil { // ctx := cloneContext(dc.ctx) // // fmt.Printf("Entering Inner-Ctx [%p]: %s\n", ctx, CtxToString(ctx, 0)) // if item, err = dc.nextFunc.Invoke(ctx, nextName, []any{dc.resource}); err == nil { // if item == nil { // err = io.EOF // } else { // dc.index++ // } // } // // fmt.Printf("Exiting Inner-Ctx [%p]: %s\n", ctx, CtxToString(ctx, 0)) // exportObjects(dc.ctx, ctx) // // fmt.Printf("Outer-Ctx [%p]: %s\n", dc.ctx, CtxToString(dc.ctx, 0)) // } else { // err = errInvalidDataSource() // } // return // } // func (dc *dataCursor) _filter(item any) (filterdItem any, err error) { // if filter, ok := dc.ds[filterName]; ok { // ctx := cloneContext(dc.ctx) // filterdItem, err = filter.Invoke(ctx, filterName, []any{item, dc.index}) // } else { // filterdItem = item // } // return // } func (dc *dataCursor) checkFilter(filter Functor, item any) (accepted bool, err error) { var v any var ok bool ctx := cloneContext(dc.ctx) if v, err = filter.Invoke(ctx, filterName, []any{item, dc.index}); 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) mappedItem, err = mapper.Invoke(ctx, mapName, []any{item, dc.index}); return } func (dc *dataCursor) Next() (item any, err error) { // must return io.EOF after the last item var accepted bool if dc.resource != nil { filter := dc.ds[filterName] mapper := dc.ds[mapName] for item == nil && err == nil { ctx := cloneContext(dc.ctx) if item, err = dc.nextFunc.Invoke(ctx, nextName, []any{dc.resource}); err == nil { if item == nil { err = io.EOF } else { dc.index++ if filter != nil { if accepted, err = dc.checkFilter(filter, item); err != nil || !accepted { item = nil } } if item != nil && mapper != nil { item, err = dc.mapItem(mapper, item) } } } exportObjects(dc.ctx, ctx) } } else { err = errInvalidDataSource() } return } func (dc *dataCursor) Index() int { return dc.index }