Added Pipe and Match parsers
This commit is contained in:
parent
4fa3c2a466
commit
edff0f8ca0
@ -6,6 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"slices"
|
"slices"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"git.codemonkeysoftware.net/b/gigaparsec/cursor"
|
"git.codemonkeysoftware.net/b/gigaparsec/cursor"
|
||||||
)
|
)
|
||||||
@ -78,14 +79,17 @@ type Message struct {
|
|||||||
expected []string
|
expected []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO rename
|
||||||
func (m Message) PosMethod() uint64 {
|
func (m Message) PosMethod() uint64 {
|
||||||
return m.pos
|
return m.pos
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO rename
|
||||||
func (m Message) GotMethod() string {
|
func (m Message) GotMethod() string {
|
||||||
return m.got
|
return m.got
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO rename
|
||||||
func (m Message) ExpectedMethod() []string {
|
func (m Message) ExpectedMethod() []string {
|
||||||
return m.expected
|
return m.expected
|
||||||
}
|
}
|
||||||
@ -95,6 +99,23 @@ func (m Message) expect(s string) Message {
|
|||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m Message) String() string {
|
||||||
|
s := fmt.Sprintf("bad parse at %d", m.pos)
|
||||||
|
if m.got != "" || len(m.expected) > 0 {
|
||||||
|
s += ":"
|
||||||
|
if m.got != "" {
|
||||||
|
s += fmt.Sprintf(" got %v", m.got)
|
||||||
|
if len(m.expected) > 0 {
|
||||||
|
s += ","
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(m.expected) > 0 {
|
||||||
|
s += fmt.Sprintf(" expected %v", strings.Join(m.expected, " or "))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
func MessageOK(pos uint64) Message { return Message{pos: pos} }
|
func MessageOK(pos uint64) Message { return Message{pos: pos} }
|
||||||
|
|
||||||
func MessageEnd(pos uint64) Message { return Message{pos: pos, got: "end of input"} }
|
func MessageEnd(pos uint64) Message { return Message{pos: pos, got: "end of input"} }
|
||||||
@ -171,9 +192,29 @@ func Satisfy[T any](pred func(T) bool) Parser[T, T] {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Slice creates a parser that attempts to read the contents of s from the input.
|
// Match creates a parser that attempts to read an input value equal to x.
|
||||||
// If Slice succeeds, it returns a copy of the matched input values.
|
// If Match succeeds, it returns the matched input value.
|
||||||
func Slice[T comparable](s []T) Parser[T, []T] {
|
func Match[T comparable](x T) Parser[T, T] {
|
||||||
|
expected := fmt.Sprint(x)
|
||||||
|
return func(state State[T]) (Result[T, T], error) {
|
||||||
|
token := make([]T, 1)
|
||||||
|
_, next, err := state.Read(token)
|
||||||
|
if errors.Is(err, io.EOF) {
|
||||||
|
return Fail[T, T](false, MessageEnd(state.Pos())), nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return Result[T, T]{}, err
|
||||||
|
}
|
||||||
|
if token[0] == x {
|
||||||
|
return Succeed(true, token[0], next, MessageOK(state.Pos())), nil
|
||||||
|
}
|
||||||
|
return Fail[T, T](false, MakeMessage(state.Pos(), fmt.Sprint(token), expected)), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchSlice creates a parser that attempts to read the contents of s from the input.
|
||||||
|
// If MatchSlice succeeds, it returns a copy of the matched input values.
|
||||||
|
func MatchSlice[T comparable](s []T) Parser[T, []T] {
|
||||||
expected := fmt.Sprint(s)
|
expected := fmt.Sprint(s)
|
||||||
return func(state State[T]) (Result[T, []T], error) {
|
return func(state State[T]) (Result[T, []T], error) {
|
||||||
token := make([]T, len(s))
|
token := make([]T, len(s))
|
||||||
@ -271,3 +312,11 @@ func end[In any](s State[In]) (Result[In, struct{}], error) {
|
|||||||
func End[In any]() Parser[In, struct{}] {
|
func End[In any]() Parser[In, struct{}] {
|
||||||
return end
|
return end
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Pipe[In, Ignore, Through any](p Parser[In, Ignore]) func(Through) Parser[In, Through] {
|
||||||
|
return func(t Through) Parser[In, Through] {
|
||||||
|
return Bind(p, func(Ignore) Parser[In, Through] {
|
||||||
|
return Return[In](t)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -43,19 +43,19 @@ func TestSlice(t *testing.T) {
|
|||||||
s := rapid.SliceOfN(rapid.Byte(), 1, -1).Draw(t, "s")
|
s := rapid.SliceOfN(rapid.Byte(), 1, -1).Draw(t, "s")
|
||||||
input := rapid.SliceOfN(rapid.Byte(), len(s), -1).
|
input := rapid.SliceOfN(rapid.Byte(), len(s), -1).
|
||||||
Filter(not(hasPrefix(s))).Draw(t, "input")
|
Filter(not(hasPrefix(s))).Draw(t, "input")
|
||||||
assertParseFails(t, input, gigaparsec.Slice(s))
|
assertParseFails(t, input, gigaparsec.MatchSlice(s))
|
||||||
}))
|
}))
|
||||||
t.Run("fails at end of input", rapid.MakeCheck(func(t *rapid.T) {
|
t.Run("fails at end of input", rapid.MakeCheck(func(t *rapid.T) {
|
||||||
s := rapid.SliceOfN(rapid.Byte(), 1, -1).Draw(t, "s")
|
s := rapid.SliceOfN(rapid.Byte(), 1, -1).Draw(t, "s")
|
||||||
inputLen := rapid.IntRange(0, len(s)-1).Draw(t, "inputLen")
|
inputLen := rapid.IntRange(0, len(s)-1).Draw(t, "inputLen")
|
||||||
input := s[:inputLen]
|
input := s[:inputLen]
|
||||||
assertParseFails(t, input, gigaparsec.Slice(s))
|
assertParseFails(t, input, gigaparsec.MatchSlice(s))
|
||||||
}))
|
}))
|
||||||
t.Run("fails when read fails", rapid.MakeCheck(func(t *rapid.T) {
|
t.Run("fails when read fails", rapid.MakeCheck(func(t *rapid.T) {
|
||||||
expectedErr := generator.Error().Draw(t, "expectedErr")
|
expectedErr := generator.Error().Draw(t, "expectedErr")
|
||||||
c := ptest.ErrCursor[byte](expectedErr)
|
c := ptest.ErrCursor[byte](expectedErr)
|
||||||
s := rapid.SliceOfN(rapid.Byte(), 0, 100).Draw(t, "s")
|
s := rapid.SliceOfN(rapid.Byte(), 0, 100).Draw(t, "s")
|
||||||
result, err := gigaparsec.Slice(s)(gigaparsec.MakeState(c))
|
result, err := gigaparsec.MatchSlice(s)(gigaparsec.MakeState(c))
|
||||||
failed, _, _ := result.Failed()
|
failed, _, _ := result.Failed()
|
||||||
test.ErrorIs(t, err, expectedErr)
|
test.ErrorIs(t, err, expectedErr)
|
||||||
test.True(t, failed)
|
test.True(t, failed)
|
||||||
@ -66,7 +66,7 @@ func TestSlice(t *testing.T) {
|
|||||||
s := input[:sLen]
|
s := input[:sLen]
|
||||||
start := gigaparsec.MakeState(cursor.NewSlice(input))
|
start := gigaparsec.MakeState(cursor.NewSlice(input))
|
||||||
|
|
||||||
result, err := gigaparsec.Slice(s)(start)
|
result, err := gigaparsec.MatchSlice(s)(start)
|
||||||
must.NoError(t, err)
|
must.NoError(t, err)
|
||||||
succeeded, consumed, value, next, _ := result.Succeeded()
|
succeeded, consumed, value, next, _ := result.Succeeded()
|
||||||
test.True(t, succeeded)
|
test.True(t, succeeded)
|
||||||
@ -89,6 +89,12 @@ func TestBind(t *testing.T) {
|
|||||||
// s := rapid.SliceOfN(rapid.Byte(), 0, 100)
|
// s := rapid.SliceOfN(rapid.Byte(), 0, 100)
|
||||||
t.Errorf("TODO")
|
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) {
|
t.Run("consumption on success", rapid.MakeCheck(func(t *rapid.T) {
|
||||||
makeParser := func(consume bool) gigaparsec.Parser[byte, struct{}] {
|
makeParser := func(consume bool) gigaparsec.Parser[byte, struct{}] {
|
||||||
return func(s gigaparsec.State[byte]) (gigaparsec.Result[byte, struct{}], error) {
|
return func(s gigaparsec.State[byte]) (gigaparsec.Result[byte, struct{}], error) {
|
||||||
|
Loading…
Reference in New Issue
Block a user