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 not requiring so much Pos() when making messages
Think about changing "consume" to "commit"
Rename Seq2 to Seq Rename Seq2 to Seq
Document Seq Document Seq
Should MakeState be private now that there's Run? 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 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. // 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] { 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 values []Out
var consumed bool var consumed bool
currState := state next := s
for { for {
result, err := p(currState) result, err := p(next)
if err != nil { if err != nil {
return Result[In, []Out]{}, fmt.Errorf("AtLeastN: %w", err) return Result[In, []Out]{}, fmt.Errorf("AtLeastN: %w", err)
} }
consumed = consumed || result.Consumed() consumed = consumed || result.Consumed()
var value Out var value Out
var success bool var success bool
success, value, nextState := result.Status() success, value, next = result.Status()
if !success { if !success {
if len(values) >= minCount { 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 return Fail[In, []Out](consumed, result.Message()), nil
} }
currState = nextState
values = append(values, value) values = append(values, value)
} }
} }

View File

@ -4,8 +4,6 @@ package gigaparsec_test
import ( import (
"bytes" "bytes"
"errors"
"io"
"testing" "testing"
"git.codemonkeysoftware.net/b/gigaparsec" "git.codemonkeysoftware.net/b/gigaparsec"
@ -140,69 +138,24 @@ func TestEnd(t *testing.T) {
} }
func TestRepeat(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) { 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") minCount := rapid.IntRange(0, maxParses).Draw(t, "minCount")
successes := rapid.IntRange(0, maxParses).Draw(t, "successes") successes := rapid.IntRange(0, maxParses).Draw(t, "successes")
shouldSucceed := successes >= minCount
input := append(ptest.SliceOfN(true, successes), false) input := append(ptest.SliceOfN(good, successes), bad)
p := gigaparsec.Repeat(minCount, gigaparsec.Match(true)) p := gigaparsec.Repeat(minCount, gigaparsec.Match(good))
result, err := p(gigaparsec.MakeState(gigaparsec.SliceReaderAt[bool](input))) result, err := p(gigaparsec.MakeState(bytes.NewReader(input)))
must.NoError(t, err) must.NoError(t, err)
success, _, next := result.Status() success, _, _ := result.Status()
test.EqOp(t, shouldSucceed, success) test.EqOp(t, successes >= minCount, success, test.Sprint("expected successes ≥ minCount"))
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)
type Token struct { t.Run("fails on error", Todo)
Consume, Succeed bool t.Run("position is unchanged on failure", Todo)
} t.Run("position follows last success on overall success", Todo)
// 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)
})
} }