diff --git a/TODO.txt b/TODO.txt index e5b6ef5..4367578 100644 --- a/TODO.txt +++ b/TODO.txt @@ -1,5 +1,3 @@ Think about changing "consume" to "commit" -Should MakeState be private now that there's Run? - I need to be able to test combinators at a lower level than Run. What's Megaparsec got that we ain't got? chainl diff --git a/bytes/regexp_test.go b/bytes/regexp_test.go index b91f61f..d54bb97 100644 --- a/bytes/regexp_test.go +++ b/bytes/regexp_test.go @@ -25,7 +25,7 @@ func TestRegexp(t *testing.T) { Draw(t, "input") p := pbytes.Regexp(string(needle)) - result, err := p(gigaparsec.MakeState(bytes.NewReader(input))) + result, err := p.RunToResult(bytes.NewReader(input)) succeeded, val, next := result.Status() must.NoError(t, err) @@ -40,7 +40,7 @@ func TestRegexp(t *testing.T) { Draw(t, "input") p := pbytes.Regexp(string(needle)) - result, err := p(gigaparsec.MakeState(bytes.NewReader(input))) + result, err := p.RunToResult(bytes.NewReader(input)) succeeded, _, _ := result.Status() must.NoError(t, err) @@ -49,21 +49,21 @@ func TestRegexp(t *testing.T) { t.Run("fails on unexpected error", func(t *testing.T) { expectedErr := errors.New("it broke") p := pbytes.Regexp("nope") - result, err := p(gigaparsec.MakeState(ptest.ErrReaderAt(expectedErr))) + result, err := p.RunToResult(ptest.ErrReaderAt(expectedErr)) succeeded, _, _ := result.Status() test.ErrorIs(t, err, expectedErr) test.False(t, succeeded) }) t.Run("returns a useful Got value", func(t *testing.T) { p := pbytes.Regexp("hello") - result, err := p(gigaparsec.MakeState(strings.NewReader("hellaparsec"))) + result, err := p.RunToResult(strings.NewReader("hellaparsec")) must.NoError(t, err) test.StrContains(t, result.Message().Got(), "hella") }) t.Run("succeeds on empty matches", func(t *testing.T) { p := pbytes.Regexp(".*") - result, err := p(gigaparsec.MakeState(strings.NewReader(""))) + result, err := p.RunToResult(strings.NewReader("")) succeeded, value, _ := result.Status() must.NoError(t, err) must.True(t, succeeded) @@ -71,7 +71,7 @@ func TestRegexp(t *testing.T) { }) t.Run("fails without an error at EOF", func(t *testing.T) { p := pbytes.Regexp("a") - result, err := p(gigaparsec.MakeState(strings.NewReader(""))) + result, err := p.RunToResult(strings.NewReader("")) succeeded, _, _ := result.Status() must.NoError(t, err) must.False(t, succeeded) diff --git a/gigaparsec.go b/gigaparsec.go index 4caa5de..dfac587 100644 --- a/gigaparsec.go +++ b/gigaparsec.go @@ -133,7 +133,8 @@ func MessageEnd(pos uint64, expected ...string) Message { return Message{pos: pos, got: "end of input", expected: expected} } -// MakeState creates a new State +// MakeState creates a new State. +// It's mostly useful for testing. You probably don't need it. func MakeState[In any](r ReaderAt[In]) State[In] { return State[In]{r: r} } @@ -225,10 +226,15 @@ func (p Parser[In, Out]) Map[Out2 any](f func(Out) Out2) Parser[In, Out2] { }) } -// Run executes a parser on r and returns the result. -func (p Parser[In, Out]) Run(r ReaderAt[In]) (out Out, err error) { +// RunToResult executes a parser on r and returns the result, or an error if reading the inputs fails. +func (p Parser[In, Out]) RunToResult(r ReaderAt[In]) (Result[In, Out], error) { start := MakeState(r) - result, err := p(start) + return p(start) +} + +// Run executes a parser on r and returns the output, or an an error if the parse fails. +func (p Parser[In, Out]) Run(r ReaderAt[In]) (out Out, err error) { + result, err := p.RunToResult(r) if err != nil { err = fmt.Errorf("Run: %w", err) return diff --git a/parser_test.go b/parser_test.go index 250ee1b..c8f77f7 100644 --- a/parser_test.go +++ b/parser_test.go @@ -31,8 +31,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(bytes.NewReader(input)) - result, err := p(start) + result, err := p.RunToResult(bytes.NewReader(input)) must.NoError(t, err) success, _, _ := result.Status() test.False(t, success) @@ -58,7 +57,7 @@ func TestSlice(t *testing.T) { expectedErr := generator.Error().Draw(t, "expectedErr") r := ptest.ErrReaderAt(expectedErr) s := rapid.SliceOfN(rapid.Byte(), 0, 100).Draw(t, "s") - result, err := gigaparsec.MatchSlice(s)(gigaparsec.MakeState(r)) + result, err := gigaparsec.MatchSlice(s).RunToResult(r) test.ErrorIs(t, err, expectedErr) success, _, _ := result.Status() test.False(t, success) @@ -67,9 +66,8 @@ func TestSlice(t *testing.T) { input := rapid.SliceOfN(rapid.Byte(), 1, -1).Draw(t, "input") s := rapid.Map(rapid.IntRange(0, len(input)), func(n int) []byte { return input[:n] }).Draw(t, "s") - start := gigaparsec.MakeState(bytes.NewReader(input)) - result, err := gigaparsec.MatchSlice(s)(start) + result, err := gigaparsec.MatchSlice(s).RunToResult(bytes.NewReader(input)) must.NoError(t, err) success, value, next := result.Status() test.True(t, success) @@ -109,7 +107,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(bytes.NewReader(nil))) + result, err := gigaparsec.Bind(p, q).RunToResult(bytes.NewReader(nil)) must.NoError(t, err) must.EqOp(t, pConsume || qConsume, result.Consumed()) })) @@ -137,8 +135,7 @@ func TestTry(t *testing.T) { } for _, c := range cases { t.Run(fmt.Sprintf("%+v", c.P), func(t *testing.T) { - start := gigaparsec.MakeState(gigaparsec.SliceReaderAt[R]{c.P}) - result, err := gigaparsec.Try(ptest.ForceResult)(start) + result, err := gigaparsec.Try(ptest.ForceResult).RunToResult(gigaparsec.SliceReaderAt[R]{c.P}) succeeded, _, _ := result.Status() must.NoError(t, err) test.EqOp(t, c.TryP.Succeed, succeeded) @@ -148,7 +145,7 @@ func TestTry(t *testing.T) { t.Run("fails on error", func(t *testing.T) { expectedErr := errors.New("it broke") p := gigaparsec.Try(gigaparsec.Match(byte(0))) - result, err := p(gigaparsec.MakeState(ptest.ErrReaderAt(expectedErr))) + result, err := p.RunToResult(ptest.ErrReaderAt(expectedErr)) succeeded, _, _ := result.Status() test.ErrorIs(t, err, expectedErr) test.False(t, succeeded) @@ -172,7 +169,7 @@ func TestRepeat(t *testing.T) { input := append(ptest.SliceOfN(true, successes), false) p := gigaparsec.Repeat(minCount, gigaparsec.Match(true)) - result, err := p(gigaparsec.MakeState(gigaparsec.SliceReaderAt[bool](input))) + result, err := p.RunToResult(gigaparsec.SliceReaderAt[bool](input)) must.NoError(t, err) success, _, next := result.Status() @@ -190,21 +187,21 @@ func TestRepeat(t *testing.T) { } shouldConsume := consumeAt != nil - result, err := gigaparsec.Repeat(0, ptest.ForceResult)(gigaparsec.MakeState(gigaparsec.SliceReaderAt[ptest.ForcedResult](input))) + result, err := gigaparsec.Repeat(0, ptest.ForceResult).RunToResult(gigaparsec.SliceReaderAt[ptest.ForcedResult](input)) must.NoError(t, err) test.EqOp(t, shouldConsume, result.Consumed()) })) t.Run("does not consume on empty input", func(t *testing.T) { p := gigaparsec.Repeat(0, gigaparsec.Match(0)) - result, err := p(gigaparsec.MakeState(gigaparsec.SliceReaderAt[int](nil))) + result, err := p.RunToResult(gigaparsec.SliceReaderAt[int](nil)) must.NoError(t, err) must.False(t, result.Consumed()) }) t.Run("fails on error", func(t *testing.T) { expectedErr := errors.New("it broke") p := gigaparsec.Repeat(0, gigaparsec.Match(byte(0))) - result, err := p(gigaparsec.MakeState(ptest.ErrReaderAt(expectedErr))) + result, err := p.RunToResult(ptest.ErrReaderAt(expectedErr)) succeeded, _, _ := result.Status() test.ErrorIs(t, err, expectedErr) test.False(t, succeeded)