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 Write Repeat tests
Clean up cursor tests
Combine Cursor with State
Think about not requiring so much Pos() when making messages Think about not requiring so much Pos() when making messages
Rename Seq2 to Seq Rename Seq2 to Seq
Document Seq Document Seq

View File

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

View File

@ -3,7 +3,6 @@
package cursor package cursor
import ( import (
"errors"
"io" "io"
) )
@ -33,36 +32,50 @@ type Cursor[Datum any] interface {
At(pos uint64) Cursor[Datum] At(pos uint64) Cursor[Datum]
} }
type ReaderAt[T any] interface { type SliceCursor[Datum any] struct {
ReadAt(p []T, off int64) (n int, err error) 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) { func (sc SliceCursor[Datum]) Read(dst []Datum) (n uint64, next Cursor[Datum], err error) {
if off < 0 { if sc.offset == uint64(len(sc.data)) {
return 0, errors.New("SliceReaderAt.ReadAt: negative offset") return 0, sc, io.EOF
} }
if off >= int64(len(s)) { copied := copy(dst, sc.data[sc.offset:])
return 0, io.EOF if copied < len(dst) {
}
n = copy(dst, s[off:])
if n < len(dst) {
err = io.EOF err = io.EOF
} }
return n, err n = uint64(copied)
sc.offset += n
return n, sc, err
} }
type ReaderAtCursor[T any] struct { func (sc SliceCursor[Datum]) Pos() uint64 {
r ReaderAt[T] 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 pos uint64
} }
func NewReaderAt[T any](r ReaderAt[T]) ReaderAtCursor[T] { func NewReaderAt(r io.ReaderAt) ReaderAtCursor {
return ReaderAtCursor[T]{r: r} 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)) n, err := rac.r.ReadAt(dst, int64(rac.pos))
if n > 0 { if n > 0 {
rac.pos += uint64(n) rac.pos += uint64(n)
@ -70,11 +83,11 @@ func (rac ReaderAtCursor[T]) Read(dst []T) (uint64, Cursor[T], error) {
return uint64(n), rac, err return uint64(n), rac, err
} }
func (rac ReaderAtCursor[T]) Pos() uint64 { func (rac ReaderAtCursor) Pos() uint64 {
return rac.pos return rac.pos
} }
func (rac ReaderAtCursor[T]) At(pos uint64) Cursor[T] { func (rac ReaderAtCursor) At(pos uint64) Cursor[byte] {
rac.pos = pos rac.pos = pos
return rac 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) { 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)) return cursor.NewReaderAt(bytes.NewReader(b))
}) })
t.Run("Read returns an error if the ReaderAt fails", rapid.MakeCheck(func(t *rapid.T) { 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) { func TestSlice(t *testing.T) {
assertParseFails := func(t rapid.TB, input []byte, p gigaparsec.Parser[byte, []byte]) { assertParseFails := func(t rapid.TB, input []byte, p gigaparsec.Parser[byte, []byte]) {
t.Helper() t.Helper()
start := gigaparsec.MakeState(cursor.NewReaderAt(bytes.NewReader(input))) start := gigaparsec.MakeState(cursor.NewSlice(input))
result, err := p(start) result, err := p(start)
must.NoError(t, err) must.NoError(t, err)
success, _, _ := result.Status() success, _, _ := result.Status()
@ -66,7 +66,7 @@ func TestSlice(t *testing.T) {
input := rapid.SliceOfN(rapid.Byte(), 1, -1).Draw(t, "input") input := rapid.SliceOfN(rapid.Byte(), 1, -1).Draw(t, "input")
sLen := rapid.IntRange(0, len(input)).Draw(t, "sLen") sLen := rapid.IntRange(0, len(input)).Draw(t, "sLen")
s := input[:sLen] s := input[:sLen]
start := gigaparsec.MakeState(cursor.NewReaderAt(bytes.NewReader(input))) start := gigaparsec.MakeState(cursor.NewSlice(input))
result, err := gigaparsec.MatchSlice(s)(start) result, err := gigaparsec.MatchSlice(s)(start)
must.NoError(t, err) must.NoError(t, err)
@ -108,7 +108,7 @@ func TestBind(t *testing.T) {
p := makeParser(pConsume) p := makeParser(pConsume)
q := func(struct{}) gigaparsec.Parser[byte, struct{}] { return makeParser(qConsume) } 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.NoError(t, err)
must.EqOp(t, pConsume || qConsume, result.Consumed()) 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("succeeds when number of successes is greater than minCount", Todo)
t.Run("consumes iff at least one application consumes", Todo) t.Run("consumes iff at least one application consumes", Todo)
t.Run("fails on error", 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)
} }