From edff0f8ca0a890accede84770a69245e502c6fa1 Mon Sep 17 00:00:00 2001 From: Brandon Dyck Date: Tue, 17 Sep 2024 20:10:19 -0600 Subject: [PATCH] Added Pipe and Match parsers --- gigaparsec.go | 55 +++++++++++++++++++++++++++++++++++++++++++++++--- parser_test.go | 14 +++++++++---- 2 files changed, 62 insertions(+), 7 deletions(-) diff --git a/gigaparsec.go b/gigaparsec.go index a76d56d..aa8ebc3 100644 --- a/gigaparsec.go +++ b/gigaparsec.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "slices" + "strings" "git.codemonkeysoftware.net/b/gigaparsec/cursor" ) @@ -78,14 +79,17 @@ type Message struct { expected []string } +// TODO rename func (m Message) PosMethod() uint64 { return m.pos } +// TODO rename func (m Message) GotMethod() string { return m.got } +// TODO rename func (m Message) ExpectedMethod() []string { return m.expected } @@ -95,6 +99,23 @@ func (m Message) expect(s string) Message { 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 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. -// If Slice succeeds, it returns a copy of the matched input values. -func Slice[T comparable](s []T) Parser[T, []T] { +// Match creates a parser that attempts to read an input value equal to x. +// If Match succeeds, it returns the matched input value. +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) return func(state State[T]) (Result[T, []T], error) { 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{}] { 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) + }) + } +} diff --git a/parser_test.go b/parser_test.go index 174b95c..788675d 100644 --- a/parser_test.go +++ b/parser_test.go @@ -43,19 +43,19 @@ func TestSlice(t *testing.T) { s := rapid.SliceOfN(rapid.Byte(), 1, -1).Draw(t, "s") input := rapid.SliceOfN(rapid.Byte(), len(s), -1). 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) { s := rapid.SliceOfN(rapid.Byte(), 1, -1).Draw(t, "s") inputLen := rapid.IntRange(0, len(s)-1).Draw(t, "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) { expectedErr := generator.Error().Draw(t, "expectedErr") c := ptest.ErrCursor[byte](expectedErr) 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() test.ErrorIs(t, err, expectedErr) test.True(t, failed) @@ -66,7 +66,7 @@ func TestSlice(t *testing.T) { s := input[:sLen] start := gigaparsec.MakeState(cursor.NewSlice(input)) - result, err := gigaparsec.Slice(s)(start) + result, err := gigaparsec.MatchSlice(s)(start) must.NoError(t, err) succeeded, consumed, value, next, _ := result.Succeeded() test.True(t, succeeded) @@ -89,6 +89,12 @@ func TestBind(t *testing.T) { // s := rapid.SliceOfN(rapid.Byte(), 0, 100) 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) { makeParser := func(consume bool) gigaparsec.Parser[byte, struct{}] { return func(s gigaparsec.State[byte]) (gigaparsec.Result[byte, struct{}], error) {