diff --git a/TODO.txt b/TODO.txt index 848c55f..cb49304 100644 --- a/TODO.txt +++ b/TODO.txt @@ -1,5 +1,5 @@ -Write Repeat tests Think about not requiring so much Pos() when making messages +Think about changing "consume" to "commit" Rename Seq2 to Seq Document Seq Should MakeState be private now that there's Run? diff --git a/parser_test.go b/parser_test.go index 82166da..9aa054c 100644 --- a/parser_test.go +++ b/parser_test.go @@ -4,6 +4,8 @@ package gigaparsec_test import ( "bytes" + "errors" + "io" "testing" "git.codemonkeysoftware.net/b/gigaparsec" @@ -156,7 +158,51 @@ 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") + 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))) + must.NoError(t, err) + test.EqOp(t, shouldConsume, result.Consumed()) })) - t.Run("fails on error", Todo) + 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))) + 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))) + succeeded, _, _ := result.Status() + test.ErrorIs(t, err, expectedErr) + test.False(t, succeeded) + }) }