Compare commits

..

No commits in common. "9c5e8fff0e9ce15bb678acb4c4582124e7bd6b92" and "59903ba1514812f2293ad722b66d314c96b66dd5" have entirely different histories.

3 changed files with 19 additions and 67 deletions

View File

@ -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?

View File

@ -379,26 +379,25 @@ func Pipe[In, Ignore, Through any](p Parser[In, Ignore]) func(Through) Parser[In
// It succeeds if and only if p succeeds at least minCount times.
// It consumes if and only if at least one of the applications of p consumes.
func Repeat[In, Out any](minCount int, p Parser[In, Out]) Parser[In, []Out] {
return func(state State[In]) (Result[In, []Out], error) {
return func(s State[In]) (Result[In, []Out], error) {
var values []Out
var consumed bool
currState := state
next := s
for {
result, err := p(currState)
result, err := p(next)
if err != nil {
return Result[In, []Out]{}, fmt.Errorf("AtLeastN: %w", err)
}
consumed = consumed || result.Consumed()
var value Out
var success bool
success, value, nextState := result.Status()
success, value, next = result.Status()
if !success {
if len(values) >= minCount {
return Succeed(consumed, values, currState, MessageOK(state.Pos())), nil
return Succeed(consumed, values, next, MessageOK(s.Pos())), nil
}
return Fail[In, []Out](consumed, result.Message()), nil
}
currState = nextState
values = append(values, value)
}
}

View File

@ -4,8 +4,6 @@ package gigaparsec_test
import (
"bytes"
"errors"
"io"
"testing"
"git.codemonkeysoftware.net/b/gigaparsec"
@ -140,69 +138,24 @@ func TestEnd(t *testing.T) {
}
func TestRepeat(t *testing.T) {
const maxParses = 100
t.Run("succeeds iff number of successes ≥ minCount", rapid.MakeCheck(func(t *rapid.T) {
const good byte = 'o'
const bad byte = 'x'
const maxParses = 100
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)))
input := append(ptest.SliceOfN(good, successes), bad)
p := gigaparsec.Repeat(minCount, gigaparsec.Match(good))
result, err := p(gigaparsec.MakeState(bytes.NewReader(input)))
must.NoError(t, err)
success, _, next := result.Status()
test.EqOp(t, shouldSucceed, success)
if success {
test.EqOp(t, uint64(successes), next.Pos())
}
success, _, _ := result.Status()
test.EqOp(t, successes >= minCount, success, test.Sprint("expected successes ≥ minCount"))
}))
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("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)
})
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)
}