Compare commits

..

No commits in common. "5c779c4a33031d0e09c72aa73141ec3874a86ebb" and "9375b51a705e99091d2ed0525e8b89526b3c9970" have entirely different histories.

7 changed files with 81 additions and 34 deletions

View File

@ -1,6 +1,4 @@
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,7 +4,6 @@ package bytes_test
import (
"bytes"
"strings"
"testing"
"git.codemonkeysoftware.net/b/gigaparsec"
@ -29,7 +28,7 @@ func TestRegexp(t *testing.T) {
}))
t.Run("basically works", func(t *testing.T) {
result, err := pbytes.Regexp("a")(gigaparsec.MakeState(cursor.NewReaderAt(strings.NewReader("a"))))
result, err := pbytes.Regexp("a")(gigaparsec.MakeState(cursor.NewSlice([]byte("a"))))
must.NoError(t, err)
success, value, _ := result.Status()
test.True(t, success, test.Sprint(result.Message()))
@ -39,8 +38,8 @@ func TestRegexp(t *testing.T) {
}
func TestRuneReader(t *testing.T) {
var s = "abcdefghijklmnopqrstuvwxyz"
rr := pbytes.NewRuneReader(cursor.NewReaderAt(strings.NewReader(s)))
var s = []byte("abcdefghijklmnopqrstuvwxyz")
rr := pbytes.NewRuneReader(cursor.NewSlice(s))
for i, b := range s {
r, n, err := rr.ReadRune()
test.NoError(t, err)
@ -68,7 +67,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.NewReaderAt(bytes.NewReader(input))))
result, err := pbytes.MatchString(s)(gigaparsec.MakeState(cursor.NewSlice(input)))
test.NoError(t, err)
success, _, _ := result.Status()
test.False(t, success)
@ -78,7 +77,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.NewReaderAt(bytes.NewReader(input))))
result, err := pbytes.MatchString(s)(gigaparsec.MakeState(cursor.NewSlice(input)))
must.NoError(t, err)
success, value, next := result.Status()
must.True(t, success)

View File

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

View File

@ -100,8 +100,12 @@ 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[byte] {
testCursor(t, func(b []byte) cursor.ReaderAtCursor {
return cursor.NewReaderAt(bytes.NewReader(b))
})
t.Run("Read returns an error if the ReaderAt fails", rapid.MakeCheck(func(t *rapid.T) {

26
cursor/helper.go Normal file
View File

@ -0,0 +1,26 @@
// 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
}

9
cursor/helper_test.go Normal file
View File

@ -0,0 +1,9 @@
// 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.NewReaderAt(bytes.NewReader(input)))
start := gigaparsec.MakeState(cursor.NewSlice(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.NewReaderAt(bytes.NewReader(input)))
start := gigaparsec.MakeState(cursor.NewSlice(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.NewReaderAt(bytes.NewReader(nil))))
result, err := gigaparsec.Bind(p, q)(gigaparsec.MakeState(cursor.NewSlice([]byte{})))
must.NoError(t, err)
must.EqOp(t, pConsume || qConsume, result.Consumed())
}))
@ -143,6 +143,4 @@ 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)
}