diff --git a/gigaparsec.go b/gigaparsec.go index 61afd6c..39e984b 100644 --- a/gigaparsec.go +++ b/gigaparsec.go @@ -379,25 +379,26 @@ 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(s State[In]) (Result[In, []Out], error) { + return func(state State[In]) (Result[In, []Out], error) { var values []Out var consumed bool - next := s + currState := state for { - result, err := p(next) + result, err := p(currState) if err != nil { return Result[In, []Out]{}, fmt.Errorf("AtLeastN: %w", err) } consumed = consumed || result.Consumed() var value Out var success bool - success, value, next = result.Status() + success, value, nextState := result.Status() if !success { if len(values) >= minCount { - return Succeed(consumed, values, next, MessageOK(s.Pos())), nil + return Succeed(consumed, values, currState, MessageOK(state.Pos())), nil } return Fail[In, []Out](consumed, result.Message()), nil } + currState = nextState values = append(values, value) } } diff --git a/parser_test.go b/parser_test.go index 6e44cac..82166da 100644 --- a/parser_test.go +++ b/parser_test.go @@ -138,24 +138,25 @@ 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(good, successes), bad) - p := gigaparsec.Repeat(minCount, gigaparsec.Match(good)) - result, err := p(gigaparsec.MakeState(bytes.NewReader(input))) + 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, _, _ := result.Status() - test.EqOp(t, successes >= minCount, success, test.Sprint("expected successes ≥ minCount")) + 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) { + })) - 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) }