gigaparsec/cursor/cursor.go

96 lines
2.6 KiB
Go
Raw Normal View History

package cursor
import (
"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), Read will return an error explaining why it read fewer
// bytes than requested. If Read tried to read past the end of the 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
}
type SliceCursor[Datum any] struct {
data []Datum
offset uint64
}
func NewSlice[Datum any]([]Datum) SliceCursor[Datum] { panic("not implemented") }
func (sc SliceCursor[Datum]) Read(dst []Datum) (n uint64, next Cursor[Datum], err error) {
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
}
type ReaderAtCursor struct {
r io.ReaderAt
pos uint64
}
func NewReaderAt(r io.ReaderAt) ReaderAtCursor {
return ReaderAtCursor{r: r}
}
func (rac ReaderAtCursor) Read(dst []byte) (uint64, Cursor[byte], 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) Pos() uint64 {
return rac.pos
}
// StringCursor is identical to SliceCursor[byte], but uses a string as its data source.
// The advantage is that creating a StringCursor does not require copying the source
// string into a []byte.
type StringCursor struct {
source string
offset uint64
}
func NewString(s string) StringCursor {
return StringCursor{source: s}
}
func (sc StringCursor) Read(dst []byte) (n uint64, next Cursor[byte], err error) {
copied := copy(dst, sc.source[sc.offset:])
if copied < len(dst) {
err = io.EOF
}
n = uint64(copied)
sc.offset += n
return n, sc, err
}
func (sc StringCursor) Pos() uint64 {
return sc.offset
}