diff --git a/parser_test.go b/parser_test.go index 9aa054c..70708d0 100644 --- a/parser_test.go +++ b/parser_test.go @@ -5,7 +5,7 @@ package gigaparsec_test import ( "bytes" "errors" - "io" + "fmt" "testing" "git.codemonkeysoftware.net/b/gigaparsec" @@ -127,8 +127,32 @@ func TestSatisfy(t *testing.T) { Todo(t) } -func Try(t *testing.T) { - Todo(t) +func TestTry(t *testing.T) { + type R = ptest.ForcedResult + var cases = []struct{ P, TryP R }{ + {P: R{Succeed: false, Consume: false}, TryP: R{Succeed: false, Consume: false}}, + {P: R{Succeed: false, Consume: true}, TryP: R{Succeed: false, Consume: false}}, + {P: R{Succeed: true, Consume: false}, TryP: R{Succeed: true, Consume: false}}, + {P: R{Succeed: true, Consume: true}, TryP: R{Succeed: true, Consume: true}}, + } + 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) + succeeded, _, _ := result.Status() + must.NoError(t, err) + test.EqOp(t, c.TryP.Succeed, succeeded) + test.EqOp(t, c.TryP.Consume, result.Consumed()) + }) + } + 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))) + succeeded, _, _ := result.Status() + test.ErrorIs(t, err, expectedErr) + test.False(t, succeeded) + }) } func TestLabel(t *testing.T) { @@ -158,39 +182,19 @@ func TestRepeat(t *testing.T) { } })) t.Run("consumes iff at least one application consumes", rapid.MakeCheck(func(t *rapid.T) { - type Token struct { - Consume, Succeed bool - } - // p will succeed and consume based on the values in the parsed token. - var p gigaparsec.Parser[Token, Token] = func(s gigaparsec.State[Token]) (gigaparsec.Result[Token, Token], error) { - buf := make([]Token, 1) - _, next, err := s.Read(buf) - if errors.Is(err, io.EOF) { - return gigaparsec.Fail[Token, Token](false, gigaparsec.MessageEnd(s.Pos())), nil - } - if err != nil { - return gigaparsec.Result[Token, Token]{}, err - } - tok := buf[0] - if tok.Succeed { - return gigaparsec.Succeed(tok.Consume, tok, next, gigaparsec.MessageOK(s.Pos())), nil - } else { - return gigaparsec.Fail[Token, Token](tok.Consume, gigaparsec.MakeMessage(s.Pos(), "false", "true")), nil - } - } - - input := rapid.Map(rapid.SliceOfN(rapid.Just(Token{Succeed: true}), 0, 100), - func(ts []Token) []Token { return append(ts, Token{}) }).Draw(t, "input") + input := rapid.Map(rapid.SliceOfN(rapid.Just(ptest.ForcedResult{Succeed: true}), 0, 100), + func(ts []ptest.ForcedResult) []ptest.ForcedResult { return append(ts, ptest.ForcedResult{}) }).Draw(t, "input") consumeAt := rapid.Ptr(rapid.IntRange(0, len(input)-1), true).Draw(t, "consumeAt") if consumeAt != nil { input[*consumeAt].Consume = true } shouldConsume := consumeAt != nil - result, err := gigaparsec.Repeat(0, p)(gigaparsec.MakeState(gigaparsec.SliceReaderAt[Token](input))) + result, err := gigaparsec.Repeat(0, ptest.ForceResult)(gigaparsec.MakeState(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))) diff --git a/test/helpers.go b/test/helpers.go index a47f22f..13b010e 100644 --- a/test/helpers.go +++ b/test/helpers.go @@ -4,6 +4,7 @@ package test import ( + "errors" "io" "git.codemonkeysoftware.net/b/gigaparsec" @@ -34,3 +35,22 @@ func SliceOfN[T any](value T, n int) []T { } return s } + +type ForcedResult struct{ Succeed, Consume bool } + +func ForceResult(state gigaparsec.State[ForcedResult]) (gigaparsec.Result[ForcedResult, struct{}], error) { + buf := make([]ForcedResult, 1) + _, next, err := state.Read(buf) + if errors.Is(err, io.EOF) { + return gigaparsec.Fail[ForcedResult, struct{}](false, gigaparsec.MessageEnd(state.Pos())), nil + } + if err != nil { + return gigaparsec.Result[ForcedResult, struct{}]{}, err + } + tok := buf[0] + if tok.Succeed { + return gigaparsec.Succeed(tok.Consume, struct{}{}, next, gigaparsec.MessageOK(state.Pos())), nil + } else { + return gigaparsec.Fail[ForcedResult, struct{}](tok.Consume, gigaparsec.MakeMessage(state.Pos(), "Succeed=false", "Succeed=true")), nil + } +}