2024-09-18 22:28:53 +00:00
|
|
|
// SPDX-License-Identifier: Unlicense
|
|
|
|
|
2024-09-09 16:27:54 +00:00
|
|
|
package gigaparsec_test
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2024-10-16 17:58:12 +00:00
|
|
|
"errors"
|
2024-10-17 14:57:45 +00:00
|
|
|
"fmt"
|
2024-09-09 16:27:54 +00:00
|
|
|
"testing"
|
|
|
|
|
|
|
|
"git.codemonkeysoftware.net/b/gigaparsec"
|
2024-09-13 16:38:47 +00:00
|
|
|
ptest "git.codemonkeysoftware.net/b/gigaparsec/test"
|
2024-09-13 17:17:32 +00:00
|
|
|
"git.codemonkeysoftware.net/b/gigaparsec/test/generator"
|
2024-09-09 16:27:54 +00:00
|
|
|
"github.com/shoenig/test"
|
2024-09-11 16:25:45 +00:00
|
|
|
"github.com/shoenig/test/must"
|
2024-09-09 16:27:54 +00:00
|
|
|
"pgregory.net/rapid"
|
|
|
|
)
|
|
|
|
|
|
|
|
func Todo(t *testing.T) {
|
|
|
|
t.Errorf("TODO")
|
|
|
|
}
|
|
|
|
|
2024-09-09 21:18:27 +00:00
|
|
|
func not[T any](pred func(T) bool) func(T) bool {
|
2024-09-09 16:27:54 +00:00
|
|
|
return func(x T) bool { return !pred(x) }
|
|
|
|
}
|
|
|
|
|
2024-09-10 15:30:54 +00:00
|
|
|
func hasPrefix(prefix []byte) func([]byte) bool {
|
|
|
|
return func(b []byte) bool { return bytes.HasPrefix(b, prefix) }
|
|
|
|
}
|
|
|
|
|
2024-09-09 16:27:54 +00:00
|
|
|
func TestSlice(t *testing.T) {
|
2024-09-11 16:25:45 +00:00
|
|
|
assertParseFails := func(t rapid.TB, input []byte, p gigaparsec.Parser[byte, []byte]) {
|
2024-09-09 21:13:37 +00:00
|
|
|
t.Helper()
|
2024-09-27 15:29:27 +00:00
|
|
|
start := gigaparsec.MakeState(bytes.NewReader(input))
|
2024-09-11 16:25:45 +00:00
|
|
|
result, err := p(start)
|
|
|
|
must.NoError(t, err)
|
2024-09-24 19:16:30 +00:00
|
|
|
success, _, _ := result.Status()
|
|
|
|
test.False(t, success)
|
2024-09-24 18:56:10 +00:00
|
|
|
test.False(t, result.Consumed())
|
2024-09-09 21:13:37 +00:00
|
|
|
if t.Failed() {
|
|
|
|
t.FailNow()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-09-09 16:27:54 +00:00
|
|
|
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).
|
2024-09-10 15:30:54 +00:00
|
|
|
Filter(not(hasPrefix(s))).Draw(t, "input")
|
2024-09-18 02:10:19 +00:00
|
|
|
assertParseFails(t, input, gigaparsec.MatchSlice(s))
|
2024-09-09 16:27:54 +00:00
|
|
|
}))
|
|
|
|
t.Run("fails at end of input", rapid.MakeCheck(func(t *rapid.T) {
|
|
|
|
s := rapid.SliceOfN(rapid.Byte(), 1, -1).Draw(t, "s")
|
2024-09-27 15:37:35 +00:00
|
|
|
input := rapid.Map(rapid.IntRange(0, len(s)-1),
|
|
|
|
func(n int) []byte { return s[:n] }).Draw(t, "inputLen")
|
2024-09-18 02:10:19 +00:00
|
|
|
assertParseFails(t, input, gigaparsec.MatchSlice(s))
|
2024-09-09 16:27:54 +00:00
|
|
|
}))
|
2024-09-13 16:38:47 +00:00
|
|
|
t.Run("fails when read fails", rapid.MakeCheck(func(t *rapid.T) {
|
2024-09-13 17:17:32 +00:00
|
|
|
expectedErr := generator.Error().Draw(t, "expectedErr")
|
2024-09-27 15:29:27 +00:00
|
|
|
r := ptest.ErrReaderAt(expectedErr)
|
2024-09-13 16:38:47 +00:00
|
|
|
s := rapid.SliceOfN(rapid.Byte(), 0, 100).Draw(t, "s")
|
2024-09-27 15:29:27 +00:00
|
|
|
result, err := gigaparsec.MatchSlice(s)(gigaparsec.MakeState(r))
|
2024-09-13 16:38:47 +00:00
|
|
|
test.ErrorIs(t, err, expectedErr)
|
2024-09-24 19:16:30 +00:00
|
|
|
success, _, _ := result.Status()
|
|
|
|
test.False(t, success)
|
2024-09-13 16:38:47 +00:00
|
|
|
}))
|
2024-09-09 16:27:54 +00:00
|
|
|
t.Run("succeeds when contents match", rapid.MakeCheck(func(t *rapid.T) {
|
|
|
|
input := rapid.SliceOfN(rapid.Byte(), 1, -1).Draw(t, "input")
|
2024-09-27 15:37:35 +00:00
|
|
|
s := rapid.Map(rapid.IntRange(0, len(input)),
|
|
|
|
func(n int) []byte { return input[:n] }).Draw(t, "s")
|
2024-09-27 15:29:27 +00:00
|
|
|
start := gigaparsec.MakeState(bytes.NewReader(input))
|
2024-09-09 21:00:17 +00:00
|
|
|
|
2024-09-18 02:10:19 +00:00
|
|
|
result, err := gigaparsec.MatchSlice(s)(start)
|
2024-09-11 16:25:45 +00:00
|
|
|
must.NoError(t, err)
|
2024-09-24 19:16:30 +00:00
|
|
|
success, value, next := result.Status()
|
|
|
|
test.True(t, success)
|
2024-09-24 18:56:10 +00:00
|
|
|
test.True(t, result.Consumed())
|
2024-09-11 16:25:45 +00:00
|
|
|
test.SliceEqOp(t, s, value)
|
|
|
|
test.EqOp(t, uint64(len(s)), next.Pos())
|
2024-09-09 16:27:54 +00:00
|
|
|
}))
|
|
|
|
}
|
2024-09-10 22:46:31 +00:00
|
|
|
|
|
|
|
func TestChoose(t *testing.T) {
|
|
|
|
Todo(t)
|
|
|
|
}
|
2024-09-10 22:58:34 +00:00
|
|
|
|
|
|
|
func TestBind(t *testing.T) {
|
2024-09-13 20:50:12 +00:00
|
|
|
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")
|
|
|
|
}))
|
2024-09-18 02:10:19 +00:00
|
|
|
/*
|
|
|
|
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.
|
|
|
|
*/
|
2024-09-13 20:50:12 +00:00
|
|
|
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) }
|
|
|
|
|
2024-09-27 15:29:27 +00:00
|
|
|
result, err := gigaparsec.Bind(p, q)(gigaparsec.MakeState(bytes.NewReader(nil)))
|
2024-09-13 20:50:12 +00:00
|
|
|
must.NoError(t, err)
|
|
|
|
must.EqOp(t, pConsume || qConsume, result.Consumed())
|
|
|
|
}))
|
2024-09-10 22:58:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestReturn(t *testing.T) {
|
|
|
|
Todo(t)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestMap(t *testing.T) {
|
|
|
|
Todo(t)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestSatisfy(t *testing.T) {
|
|
|
|
Todo(t)
|
|
|
|
}
|
|
|
|
|
2024-10-17 14:57:45 +00:00
|
|
|
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)
|
|
|
|
})
|
2024-09-10 22:58:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestLabel(t *testing.T) {
|
|
|
|
Todo(t)
|
|
|
|
}
|
2024-09-11 01:00:44 +00:00
|
|
|
|
|
|
|
func TestEnd(t *testing.T) {
|
|
|
|
Todo(t)
|
|
|
|
}
|
2024-09-24 18:31:38 +00:00
|
|
|
|
|
|
|
func TestRepeat(t *testing.T) {
|
2024-10-16 15:06:25 +00:00
|
|
|
const maxParses = 100
|
2024-09-27 16:40:18 +00:00
|
|
|
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")
|
2024-10-16 15:06:25 +00:00
|
|
|
shouldSucceed := successes >= minCount
|
2024-09-27 16:40:18 +00:00
|
|
|
|
2024-10-16 15:06:25 +00:00
|
|
|
input := append(ptest.SliceOfN(true, successes), false)
|
|
|
|
p := gigaparsec.Repeat(minCount, gigaparsec.Match(true))
|
|
|
|
result, err := p(gigaparsec.MakeState(gigaparsec.SliceReaderAt[bool](input)))
|
2024-09-27 16:40:18 +00:00
|
|
|
|
|
|
|
must.NoError(t, err)
|
2024-10-16 15:06:25 +00:00
|
|
|
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) {
|
2024-10-17 14:57:45 +00:00
|
|
|
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")
|
2024-10-16 17:58:12 +00:00
|
|
|
consumeAt := rapid.Ptr(rapid.IntRange(0, len(input)-1), true).Draw(t, "consumeAt")
|
|
|
|
if consumeAt != nil {
|
|
|
|
input[*consumeAt].Consume = true
|
|
|
|
}
|
|
|
|
shouldConsume := consumeAt != nil
|
|
|
|
|
2024-10-17 14:57:45 +00:00
|
|
|
result, err := gigaparsec.Repeat(0, ptest.ForceResult)(gigaparsec.MakeState(gigaparsec.SliceReaderAt[ptest.ForcedResult](input)))
|
2024-10-16 17:58:12 +00:00
|
|
|
must.NoError(t, err)
|
|
|
|
test.EqOp(t, shouldConsume, result.Consumed())
|
2024-09-27 16:40:18 +00:00
|
|
|
}))
|
2024-10-17 14:57:45 +00:00
|
|
|
|
2024-10-16 17:58:12 +00:00
|
|
|
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)
|
|
|
|
})
|
2024-09-24 18:31:38 +00:00
|
|
|
}
|