// Package iterator defines a generic way to iterator over a slice of data. package iterator import "fmt" // An Iterator traverses over slices. type Iterator[T any] struct { items []T index int } // Of creates a new iterator, over a set of defined items. func Of[T any](items []T) *Iterator[T] { return &Iterator[T]{items: items, index: 0} } // Index returns the current position of the iterator. func (i Iterator[T]) Index() int { return i.index } // Get returns the datum at the current position of the iterator. func (i Iterator[T]) Get() (T, error) { var null T if i.Done() { return null, fmt.Errorf("iterator is exhausted") } return i.items[i.index], nil } // MustGet is a version of Get, that panics if the datum cannot be returned. func (i Iterator[T]) MustGet() T { t, err := i.Get() if err != nil { panic(fmt.Errorf("cannot get current token: %w", err)) } return t } // Forward increments the iterator if the iterator is not yet at the end of the // slice. func (i *Iterator[T]) Forward() { if !i.Done() { i.index++ } } // Next attempts to increment the iterator. Returns an error if it cannot be // incremented. func (i *Iterator[T]) Next() (T, error) { item, err := i.Get() if err == nil { i.index++ } return item, err } // Back decrements the iterator. If the iterator is already at the beginning of // the slice, this is a no-op. func (i *Iterator[T]) Back() { i.index = max(i.index-1, 0) } // Done returns whether the iterator is at the end of the slice or not. func (i Iterator[T]) Done() bool { return i.index == len(i.items) } // While increments the iterator as long as the current item satisfies the // predicate. The first item that does not match is left unconsumed. func (i *Iterator[T]) While(fn func(T) bool) { for !i.Done() { if !fn(i.MustGet()) { return } i.Forward() } } // Try attempts to perform an operation using the iterator. If the operation // succeeds, the iterator keeps its new position. If the operation fails, the // iterator is rolled back, and an error is returned. func Try[T any, U any](i *Iterator[T], fn func(i *Iterator[T]) (U, error)) (U, error) { saved := i.index out, err := fn(i) if err != nil { i.index = saved } return out, err }