github.com/haraldrudell/parl@v0.4.176/iters/converter.go (about)

     1  /*
     2  © 2022–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/)
     3  ISC License
     4  */
     5  
     6  package iters
     7  
     8  import (
     9  	"github.com/haraldrudell/parl/internal/cyclebreaker"
    10  	"github.com/haraldrudell/parl/perrors"
    11  )
    12  
    13  // Converter traverses another iterator and returns converted values
    14  type Converter[K any, V any] struct {
    15  	// keyIterator provides the key values converterFunction uses to
    16  	// return values
    17  	keyIterator Iterator[K]
    18  	// ConverterFunction receives a key and returns the corresponding value
    19  	//	- func(key K, isCancel bool) (value V, err error)
    20  	//   - if isCancel true, it means this is the last invocation of ConverterFunction and
    21  	//     ConverterFunction should release any resources.
    22  	//     Any returned value is not used
    23  	//   - ConverterFunction signals end of values by returning parl.ErrEndCallbacks.
    24  	//     if hasValue is true, the accompanying value is used
    25  	//   - when ConverterFunction returns error, it will not be invoked again.
    26  	//     For errors other than parl.ErrEndCallbacks, value is not used
    27  	//   - ConverterFunction must be thread-safe
    28  	//   - ConverterFunction is invoked by at most one thread at a time
    29  	converter ConverterFunction[K, V]
    30  	*BaseIterator[V]
    31  }
    32  
    33  // NewConverterIterator returns a converting iterator.
    34  //   - converterFunction receives cancel and can return error
    35  //   - ConverterIterator is thread-safe and re-entrant.
    36  //   - stores self-referencing pointers
    37  func NewConverterIterator[K any, V any](
    38  	keyIterator Iterator[K],
    39  	converter ConverterFunction[K, V],
    40  	asyncCancel ...func(),
    41  ) (iterator Iterator[V]) {
    42  	if converter == nil {
    43  		panic(cyclebreaker.NilError("converter"))
    44  	} else if keyIterator == nil {
    45  		panic(cyclebreaker.NilError("keyIterator"))
    46  	}
    47  
    48  	c := Converter[K, V]{
    49  		keyIterator: keyIterator,
    50  		converter:   converter,
    51  	}
    52  	c.BaseIterator = NewBaseIterator(c.iteratorAction, asyncCancel...)
    53  
    54  	return &c
    55  }
    56  
    57  // Init implements the right-hand side of a short variable declaration in
    58  // the init statement for a Go “for” clause
    59  //
    60  // Usage:
    61  //
    62  //		for i, iterator := NewSlicePointerIterator(someSlice).Init(); iterator.Cond(&i); {
    63  //	   // i is pointer to slice element
    64  func (i *Converter[K, T]) Init() (iterationVariable T, iterator Iterator[T]) {
    65  	iterator = i
    66  	return
    67  }
    68  
    69  // iteratorAction invokes converterFunction recovering a possible panic
    70  //   - if cancelState == notCanceled, a new value is requested.
    71  //     Otherwise, iteration cancel is requested
    72  //   - if err is nil, value is valid and isPanic false.
    73  //     Otherwise, err is non-nil and isPanic may be set.
    74  //     value is zero-value
    75  func (i *Converter[K, T]) iteratorAction(isCancel bool) (value T, err error) {
    76  
    77  	// get next key from keyIterator
    78  	var key K
    79  	var isEndOfKeys bool
    80  	if !isCancel {
    81  		var hasKey bool
    82  		if key, hasKey = i.keyIterator.Next(); !hasKey {
    83  			isEndOfKeys = true
    84  			isCancel = true
    85  		}
    86  	}
    87  
    88  	// invoke converter function
    89  	//	- func(key K, isCancel bool) (value V, err error)
    90  	if value, err = i.converter(key, isCancel); err != nil {
    91  		err = perrors.Stack(err)
    92  	} else if isEndOfKeys {
    93  		err = cyclebreaker.ErrEndCallbacks
    94  	}
    95  
    96  	return
    97  }