// SPDX-License-Identifier: Unlicense package gigaparsec_test import ( "bytes" "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") inputLen := rapid.IntRange(0, len(s)-1).Draw(t, "inputLen") input := s[: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") sLen := rapid.IntRange(0, len(input)).Draw(t, "sLen") s := input[:sLen] 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 Try(t *testing.T) { Todo(t) } func TestLabel(t *testing.T) { Todo(t) } func TestEnd(t *testing.T) { Todo(t) } func TestRepeat(t *testing.T) { t.Run("fails when number of successes is less than minCount", Todo) t.Run("succeeds when number of successes is greater than minCount", Todo) t.Run("consumes iff at least one application consumes", Todo) t.Run("fails on error", Todo) t.Run("position is unchanged on failure", Todo) t.Run("position follows last success on overall success", Todo) }