Compare commits

...

4 Commits

Author SHA1 Message Date
5c779c4a33 Removed BufferedReaderAt 2024-09-25 17:00:29 -06:00
412707c2b5 Removed SliceCursor 2024-09-25 16:56:48 -06:00
c0603b1c30 Added a generic ReaderAt 2024-09-25 16:34:31 -06:00
e61bf67b85 More todos 2024-09-25 15:50:28 -06:00
7 changed files with 34 additions and 81 deletions

View File

@ -1,4 +1,6 @@
Write Repeat tests
Clean up cursor tests
Combine Cursor with State
Think about not requiring so much Pos() when making messages
Rename Seq2 to Seq
Document Seq

View File

@ -4,6 +4,7 @@ package bytes_test
import (
"bytes"
"strings"
"testing"
"git.codemonkeysoftware.net/b/gigaparsec"
@ -28,7 +29,7 @@ func TestRegexp(t *testing.T) {
}))
t.Run("basically works", func(t *testing.T) {
result, err := pbytes.Regexp("a")(gigaparsec.MakeState(cursor.NewSlice([]byte("a"))))
result, err := pbytes.Regexp("a")(gigaparsec.MakeState(cursor.NewReaderAt(strings.NewReader("a"))))
must.NoError(t, err)
success, value, _ := result.Status()
test.True(t, success, test.Sprint(result.Message()))
@ -38,8 +39,8 @@ func TestRegexp(t *testing.T) {
}
func TestRuneReader(t *testing.T) {
var s = []byte("abcdefghijklmnopqrstuvwxyz")
rr := pbytes.NewRuneReader(cursor.NewSlice(s))
var s = "abcdefghijklmnopqrstuvwxyz"
rr := pbytes.NewRuneReader(cursor.NewReaderAt(strings.NewReader(s)))
for i, b := range s {
r, n, err := rr.ReadRune()
test.NoError(t, err)
@ -67,7 +68,7 @@ func TestMatchString(t *testing.T) {
notPrefix := func(b []byte) bool { return !bytes.HasPrefix(input, b) }
s := string(bgen.Filter(notPrefix).Draw(t, "s"))
result, err := pbytes.MatchString(s)(gigaparsec.MakeState(cursor.NewSlice(input)))
result, err := pbytes.MatchString(s)(gigaparsec.MakeState(cursor.NewReaderAt(bytes.NewReader(input))))
test.NoError(t, err)
success, _, _ := result.Status()
test.False(t, success)
@ -77,7 +78,7 @@ func TestMatchString(t *testing.T) {
input := rapid.SliceOfN(rapid.Byte(), 1, 100).Draw(t, "input")
slen := rapid.IntRange(0, len(input)).Draw(t, "slen")
s := string(input[:slen])
result, err := pbytes.MatchString(s)(gigaparsec.MakeState(cursor.NewSlice(input)))
result, err := pbytes.MatchString(s)(gigaparsec.MakeState(cursor.NewReaderAt(bytes.NewReader(input))))
must.NoError(t, err)
success, value, next := result.Status()
must.True(t, success)

View File

@ -3,6 +3,7 @@
package cursor
import (
"errors"
"io"
)
@ -32,50 +33,36 @@ type Cursor[Datum any] interface {
At(pos uint64) Cursor[Datum]
}
type SliceCursor[Datum any] struct {
data []Datum
offset uint64
type ReaderAt[T any] interface {
ReadAt(p []T, off int64) (n int, err error)
}
func NewSlice[Datum any](data []Datum) SliceCursor[Datum] {
return SliceCursor[Datum]{
data: data,
offset: 0,
}
}
type SliceReaderAt[T any] []T
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
func (s SliceReaderAt[T]) ReadAt(dst []T, off int64) (n int, err error) {
if off < 0 {
return 0, errors.New("SliceReaderAt.ReadAt: negative offset")
}
copied := copy(dst, sc.data[sc.offset:])
if copied < len(dst) {
if off >= int64(len(s)) {
return 0, io.EOF
}
n = copy(dst, s[off:])
if n < len(dst) {
err = io.EOF
}
n = uint64(copied)
sc.offset += n
return n, sc, err
return n, 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 struct {
r io.ReaderAt
type ReaderAtCursor[T any] struct {
r ReaderAt[T]
pos uint64
}
func NewReaderAt(r io.ReaderAt) ReaderAtCursor {
return ReaderAtCursor{r: r}
func NewReaderAt[T any](r ReaderAt[T]) ReaderAtCursor[T] {
return ReaderAtCursor[T]{r: r}
}
func (rac ReaderAtCursor) Read(dst []byte) (uint64, Cursor[byte], error) {
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)
@ -83,11 +70,11 @@ func (rac ReaderAtCursor) Read(dst []byte) (uint64, Cursor[byte], error) {
return uint64(n), rac, err
}
func (rac ReaderAtCursor) Pos() uint64 {
func (rac ReaderAtCursor[T]) Pos() uint64 {
return rac.pos
}
func (rac ReaderAtCursor) At(pos uint64) Cursor[byte] {
func (rac ReaderAtCursor[T]) At(pos uint64) Cursor[T] {
rac.pos = pos
return rac
}

View File

@ -100,12 +100,8 @@ func testCursor[C cursor.Cursor[byte]](t *testing.T, makeCursor func([]byte) C)
}))
}
func TestSliceCursor(t *testing.T) {
testCursor(t, cursor.NewSlice[byte])
}
func TestReaderAtCursor(t *testing.T) {
testCursor(t, func(b []byte) cursor.ReaderAtCursor {
testCursor(t, func(b []byte) cursor.ReaderAtCursor[byte] {
return cursor.NewReaderAt(bytes.NewReader(b))
})
t.Run("Read returns an error if the ReaderAt fails", rapid.MakeCheck(func(t *rapid.T) {

View File

@ -1,26 +0,0 @@
// SPDX-License-Identifier: Unlicense
package cursor
import (
"io"
)
// BufferedReaderAt uses a buffer to supplement an io.Reader
// with limited backward seeking.
type BufferedReaderAt struct{}
func NewBufferedReaderAt(r io.Reader, minBuffer uint64) *BufferedReaderAt {
return nil
}
// ReadAt reads bytes from the underlying reader. If the offset is after
// the end of the buffer, ReadAt will first read and ignore bytes from the
// underlying reader until it reaches the offset. If the offset is
// before the start of the buffer, ReadAt will return an error.
//
// If your parser needs unlimited lookahead, you should probably
// just read the whole input into a slice and use BytesCursor.
func (b *BufferedReaderAt) ReadAt(dst []byte, offset int64) (int, error) {
return 0, nil
}

View File

@ -1,9 +0,0 @@
// SPDX-License-Identifier: Unlicense
package cursor_test
import "testing"
func TestBufferedReader(t *testing.T) {
Todo(t)
}

View File

@ -30,7 +30,7 @@ func hasPrefix(prefix []byte) func([]byte) bool {
func TestSlice(t *testing.T) {
assertParseFails := func(t rapid.TB, input []byte, p gigaparsec.Parser[byte, []byte]) {
t.Helper()
start := gigaparsec.MakeState(cursor.NewSlice(input))
start := gigaparsec.MakeState(cursor.NewReaderAt(bytes.NewReader(input)))
result, err := p(start)
must.NoError(t, err)
success, _, _ := result.Status()
@ -66,7 +66,7 @@ func TestSlice(t *testing.T) {
input := rapid.SliceOfN(rapid.Byte(), 1, -1).Draw(t, "input")
sLen := rapid.IntRange(0, len(input)).Draw(t, "sLen")
s := input[:sLen]
start := gigaparsec.MakeState(cursor.NewSlice(input))
start := gigaparsec.MakeState(cursor.NewReaderAt(bytes.NewReader(input)))
result, err := gigaparsec.MatchSlice(s)(start)
must.NoError(t, err)
@ -108,7 +108,7 @@ func TestBind(t *testing.T) {
p := makeParser(pConsume)
q := func(struct{}) gigaparsec.Parser[byte, struct{}] { return makeParser(qConsume) }
result, err := gigaparsec.Bind(p, q)(gigaparsec.MakeState(cursor.NewSlice([]byte{})))
result, err := gigaparsec.Bind(p, q)(gigaparsec.MakeState(cursor.NewReaderAt(bytes.NewReader(nil))))
must.NoError(t, err)
must.EqOp(t, pConsume || qConsume, result.Consumed())
}))
@ -143,4 +143,6 @@ func TestRepeat(t *testing.T) {
t.Run("succeeds when number of successes is greater than minCount", Todo)
t.Run("consumes iff at least one application consumes", Todo)
t.Run("fails on error", Todo)
t.Run("position is unchanged on failure", Todo)
t.Run("position follows last success on overall success", Todo)
}