diff --git a/gigaparsec.go b/gigaparsec.go index 5762d41..19e20de 100644 --- a/gigaparsec.go +++ b/gigaparsec.go @@ -48,6 +48,13 @@ func (s State[T]) Pos() uint64 { return s.cursor.Pos() } +func Parse[In, Out any](p Parser[In, Out], c cursor.Cursor[In]) (result Out, err error) { + st := State[In]{cursor: c} + var reply Result[In, Out] + _, reply, err = p(st) + return reply.Value, err +} + type Parser[In, Out any] func(State[In]) (consumed bool, reply Result[In, Out], err error) func Return[In, Out any](value Out) Parser[In, Out] { diff --git a/go.mod b/go.mod index ba08155..53000a0 100644 --- a/go.mod +++ b/go.mod @@ -8,3 +8,5 @@ require ( ) require github.com/google/go-cmp v0.6.0 // indirect + +replace github.com/shoenig/test v1.10.0 => ../shoenig-test diff --git a/parser_test.go b/parser_test.go new file mode 100644 index 0000000..74c6409 --- /dev/null +++ b/parser_test.go @@ -0,0 +1,52 @@ +package gigaparsec_test + +import ( + "bytes" + "testing" + + "git.codemonkeysoftware.net/b/gigaparsec" + "git.codemonkeysoftware.net/b/gigaparsec/cursor" + "github.com/shoenig/test" + "pgregory.net/rapid" +) + +func Todo(t *testing.T) { + t.Errorf("TODO") +} + +func notP[T any](pred func(T) bool) func(T) bool { + return func(x T) bool { return !pred(x) } +} + +func hasPrefix(prefix []byte) func([]byte) bool { + return func(b []byte) bool { return bytes.HasPrefix(b, prefix) } +} + +func TestSlice(t *testing.T) { + t.Run("fails with wrong contents", rapid.MakeCheck(func(t *rapid.T) { + s := rapid.SliceOfN(rapid.Byte(), 1, -1).Draw(t, "s") + input := rapid.SliceOfN(rapid.Byte(), len(s), -1). + Filter(notP(hasPrefix(s))).Draw(t, "input") + out, err := gigaparsec.Parse(gigaparsec.Slice(s), cursor.NewSlice(input)) + test.ErrorAs(t, err, &gigaparsec.ParseError{}) + test.SliceEmpty(t, out) + })) + t.Run("fails at end of input", rapid.MakeCheck(func(t *rapid.T) { + s := rapid.SliceOfN(rapid.Byte(), 1, -1).Draw(t, "s") + inputLen := rapid.IntRange(0, len(s)-1).Draw(t, "inputLen") + input := s[:inputLen] + out, err := gigaparsec.Parse(gigaparsec.Slice(s), cursor.NewSlice(input)) + test.ErrorAs(t, err, &gigaparsec.ParseError{}) + test.SliceEmpty(t, out) + })) + t.Run("fails when read fails", Todo) + t.Run("succeeds when contents match", rapid.MakeCheck(func(t *rapid.T) { + input := rapid.SliceOfN(rapid.Byte(), 1, -1).Draw(t, "input") + sLen := rapid.IntRange(0, len(input)).Draw(t, "sLen") + s := input[:sLen] + out, err := gigaparsec.Parse(gigaparsec.Slice(s), cursor.NewSlice(input)) + test.NoError(t, err) + test.SliceEqOp(t, s, out) + })) + t.Run("next state has correct position", Todo) +}