// SPDX-License-Identifier: Unlicense package gigaparsec_test import ( "bytes" "errors" "fmt" "testing" "git.codemonkeysoftware.net/b/gigaparsec" ptest "git.codemonkeysoftware.net/b/gigaparsec/test" "git.codemonkeysoftware.net/b/gigaparsec/test/generator" "github.com/shoenig/test" "github.com/shoenig/test/must" "pgregory.net/rapid" ) func Todo(t *testing.T) { t.Errorf("TODO") } func not[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) { assertParseFails := func(t rapid.TB, input []byte, p gigaparsec.Parser[byte, []byte]) { t.Helper() start := gigaparsec.MakeState(bytes.NewReader(input)) result, err := p(start) must.NoError(t, err) success, _, _ := result.Status() test.False(t, success) test.False(t, result.Consumed()) if t.Failed() { t.FailNow() } } 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(not(hasPrefix(s))).Draw(t, "input") assertParseFails(t, input, gigaparsec.MatchSlice(s)) })) t.Run("fails at end of input", rapid.MakeCheck(func(t *rapid.T) { s := rapid.SliceOfN(rapid.Byte(), 1, -1).Draw(t, "s") input := rapid.Map(rapid.IntRange(0, len(s)-1), func(n int) []byte { return s[:n] }).Draw(t, "inputLen") assertParseFails(t, input, gigaparsec.MatchSlice(s)) })) t.Run("fails when read fails", rapid.MakeCheck(func(t *rapid.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)) test.ErrorIs(t, err, expectedErr) success, _, _ := result.Status() test.False(t, success) })) t.Run("succeeds when contents match", rapid.MakeCheck(func(t *rapid.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) must.NoError(t, err) success, value, next := result.Status() test.True(t, success) test.True(t, result.Consumed()) test.SliceEqOp(t, s, value) test.EqOp(t, uint64(len(s)), next.Pos()) })) } func TestChoose(t *testing.T) { Todo(t) } func TestBind(t *testing.T) { t.Run("fails without constructing second parser if the first parser fails", Todo) t.Run("returns an error without constructing second parser if the first returns an error", Todo) t.Run("fails if the second parser fails", Todo) t.Run("returns an error if the second parser returns an error", Todo) t.Run("succeeds if both parsers succeed", rapid.MakeCheck(func(t *rapid.T) { // s := rapid.SliceOfN(rapid.Byte(), 0, 100) t.Errorf("TODO") })) /* If the first parser fails, then consumption depends on the first parser. If the first parser succeeds, then bound parser consumes iff either parser succeeded. For BindN: If all parsers before i succeed, bound parser consumes if any parser before i consumed. */ t.Run("consumption on success", rapid.MakeCheck(func(t *rapid.T) { makeParser := func(consume bool) gigaparsec.Parser[byte, struct{}] { return func(s gigaparsec.State[byte]) (gigaparsec.Result[byte, struct{}], error) { return gigaparsec.Succeed(consume, struct{}{}, s, gigaparsec.MessageOK(0)), nil } } pConsume := rapid.Bool().Draw(t, "pShouldConsume") qConsume := rapid.Bool().Draw(t, "qShouldConsume") p := makeParser(pConsume) q := func(struct{}) gigaparsec.Parser[byte, struct{}] { return makeParser(qConsume) } result, err := gigaparsec.Bind(p, q)(gigaparsec.MakeState(bytes.NewReader(nil))) must.NoError(t, err) must.EqOp(t, pConsume || qConsume, result.Consumed()) })) } func TestReturn(t *testing.T) { Todo(t) } func TestMap(t *testing.T) { Todo(t) } func TestSatisfy(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) { Todo(t) } func TestEnd(t *testing.T) { Todo(t) } func TestRepeat(t *testing.T) { const maxParses = 100 t.Run("succeeds iff number of successes ≥ minCount", rapid.MakeCheck(func(t *rapid.T) { minCount := rapid.IntRange(0, maxParses).Draw(t, "minCount") successes := rapid.IntRange(0, maxParses).Draw(t, "successes") shouldSucceed := successes >= minCount input := append(ptest.SliceOfN(true, successes), false) p := gigaparsec.Repeat(minCount, gigaparsec.Match(true)) result, err := p(gigaparsec.MakeState(gigaparsec.SliceReaderAt[bool](input))) must.NoError(t, err) success, _, next := result.Status() test.EqOp(t, shouldSucceed, success) if success { test.EqOp(t, uint64(successes), next.Pos()) } })) t.Run("consumes iff at least one application consumes", rapid.MakeCheck(func(t *rapid.T) { 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, 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))) 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) }) }