// SPDX-License-Identifier: Unlicense package cursor import ( "errors" "io" ) // Cursor reads data from a specific spot in a data source. type Cursor[Datum any] interface { // I almost parameterized Cursor by its implementation (i.e. the Curiously // Recurring Template Pattern), but then each parser would need that parameter. // That might work well in a language with much stronger type inference, but // not in Go. The upside would have been that for each implementation Impl, // Impl.Read could have returned an unboxed Impl, which would have slightly // simplified testing and maybe slightly reduced allocs. // Read fill dst with data from this Cursor's position in the underlying // source. It returns the number of data it read and a new Cursor for // the position at which the read ended, or an error if the read failed. // All calls to a given Cursor will return data from the same position. // If n < len(dst) or if the cursor's position is at the end of the data source, // Read will return an error explaining why it read fewer bytes than requested. // If the error was due to the cursor reaching the end of the data source, // err will be io.EOF. Read(dst []Datum) (n uint64, next Cursor[Datum], err error) // Pos returns the Cursor's position within the source. Pos() uint64 // At returns a new cursor at the position pos. At(pos uint64) Cursor[Datum] } type ReaderAt[T any] interface { ReadAt(p []T, off int64) (n int, err error) } type SliceReaderAt[T any] []T func (s SliceReaderAt[T]) ReadAt(dst []T, off int64) (n int, err error) { if off < 0 { return 0, errors.New("SliceReaderAt.ReadAt: negative offset") } if off >= int64(len(s)) { return 0, io.EOF } n = copy(dst, s[off:]) if n < len(dst) { err = io.EOF } return n, err } type SliceCursor[Datum any] struct { data []Datum offset uint64 } func NewSlice[Datum any](data []Datum) SliceCursor[Datum] { return SliceCursor[Datum]{ data: data, offset: 0, } } func (sc SliceCursor[Datum]) Read(dst []Datum) (n uint64, next Cursor[Datum], err error) { if sc.offset == uint64(len(sc.data)) { return 0, sc, io.EOF } copied := copy(dst, sc.data[sc.offset:]) if copied < len(dst) { err = io.EOF } n = uint64(copied) sc.offset += n return n, sc, err } func (sc SliceCursor[Datum]) Pos() uint64 { return sc.offset } func (sc SliceCursor[Datum]) At(pos uint64) Cursor[Datum] { sc.offset = pos return sc } type ReaderAtCursor[T any] struct { r ReaderAt[T] pos uint64 } func NewReaderAt[T any](r ReaderAt[T]) ReaderAtCursor[T] { return ReaderAtCursor[T]{r: r} } func (rac ReaderAtCursor[T]) Read(dst []T) (uint64, Cursor[T], error) { n, err := rac.r.ReadAt(dst, int64(rac.pos)) if n > 0 { rac.pos += uint64(n) } return uint64(n), rac, err } func (rac ReaderAtCursor[T]) Pos() uint64 { return rac.pos } func (rac ReaderAtCursor[T]) At(pos uint64) Cursor[T] { rac.pos = pos return rac }