Compare commits

...
32 Commits
Author SHA1 Message Date
b c7a743b07e Apostrophes are serious business
Test and check / test-and-check (push) Canceled after 0s
2026-08-21 23:11:15 -06:00
b 9b25034a01 Use a lot less MakeState
Test and check / test-and-check (push) Canceled after 0s
2026-08-21 23:08:51 -06:00
b 17b2a0eee5 Don't require so much Pos() when making messages 2026-08-21 22:36:36 -06:00
b cc25ac923f Check and test code on psuh
Test and check / test-and-check (push) Failing after 1m34s
2026-08-21 18:21:16 -06:00
b 3404e39dea Hello world Actions workflow
Test and check / run-tests (push) Successful in 18s
2026-08-21 00:33:23 -06:00
b c9c2251e31 Tune up bytes example 2026-08-20 23:21:58 -06:00
b 22a6eb7335 Move Map and Run onto Parser, and document stuff 2026-08-20 23:14:00 -06:00
b 6b201fbb69 Finish and test Token combinator 2026-08-20 22:54:31 -06:00
b 5d1f4940ce Write doc comments for the Seq family 2026-08-20 20:41:56 -06:00
b 7560d53d62 Finish renaming Seq2 to Seq 2026-08-20 20:35:40 -06:00
b 18ad9c301a Let Todo take other types than *testing.T 2026-08-20 20:35:07 -06:00
b f7ddbb8fc4 Rename Seq2 to Seq 2026-07-24 16:25:29 -06:00
b e60f6ae015 Added Bracket, Where, and Token (without tests) 2025-04-11 19:03:45 -06:00
b 035fa7da14 Added Lazy combinator 2025-04-02 15:50:12 -06:00
b 82ed6b5546 Regexp: don't return error when match fails at EOF 2025-04-02 14:24:21 -06:00
b 4e157f7a0e Regexp: succeed on empty match at end of input 2025-04-02 14:06:12 -06:00
b 1892a97070 Add some TODOs 2025-04-02 13:27:16 -06:00
b 981edc92f7 Add Seq benchmarks 2024-11-29 23:38:38 -07:00
b 74237e2593 Merge branch 'master' of git.codemonkeysoftware.net:b/gigaparsec 2024-11-29 11:00:38 -07:00
b 17c468a3f7 Add naïve Seq series 2024-11-29 11:00:34 -07:00
b ac739c0f3b Update todo list 2024-11-29 08:04:50 +00:00
b b3a6bfc02e Don't be so negative, man 2024-11-29 00:55:59 -07:00
b c85d0d280e Move naïve Bind and Seq into a separate package 2024-11-29 00:53:11 -07:00
b fa6c15566d Fix formatting in inspiration.md 2024-11-28 22:11:05 +00:00
b 13d83e70ad Add missing SPDX headers 2024-10-17 16:02:29 -06:00
b 56536b04f6 Turn up Bind/Seq count and write a simple Bind benchmark 2024-10-17 16:01:27 -06:00
b bc2f7aa911 Added Try tests 2024-10-17 08:57:45 -06:00
b abef123f8a Removed unused Todo function 2024-10-16 13:52:13 -06:00
b f3a37f5fb6 Test Regexp failure message 2024-10-16 13:50:14 -06:00
b c22246b7de Added some Regexp tests 2024-10-16 13:32:25 -06:00
b 9c5e8fff0e Added Repeat tests 2024-10-16 11:58:12 -06:00
b bfc9a9ae58 Test Repeat success and next state 2024-10-16 09:06:25 -06:00
18 changed files with 1560 additions and 133 deletions
+18
View File
@@ -0,0 +1,18 @@
name: Test and check
on:
- push
jobs:
test-and-check:
runs-on: brandon-ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version-file: go.mod
cache-dependency-path: go.sum
- name: Check SPDX headers
run: go run ./internal/check
- name: Run unit tests
run: go test ./...
+2 -2
View File
@@ -1,8 +1,8 @@
# Gigaparsec
[![Go Reference](https://pkg.go.dev/badge/git.codemonkeysoftware.net/b/gigaparsec.svg)](https://pkg.go.dev/git.codemonkeysoftware.net/b/gigaparsec)
![Total garbage.](https://img.shields.io/badge/Total-garbage-red)
by Brandon Dyck <[brandon@dyck.us](mailto:brandon@dyck.us)>
Monadic parser combinators in Go
**I don't recommend using this yet. It is very unfinished and it will break.**
+3 -6
View File
@@ -1,6 +1,3 @@
Write Repeat tests
Think about not requiring so much Pos() when making messages
Rename Seq2 to Seq
Document Seq
Should MakeState be private now that there's Run?
What's Megaparsec got that we ain't got?
Think about changing "consume" to "commit"
Whats Megaparsec got that we aint got?
chainl
+424 -6
View File
@@ -4,7 +4,7 @@
package gigaparsec
// Bind combines p with a parser created by f that depends on p's result value.
// Bind combines p with a parser created by f that depends on ps result value.
func Bind[In, Out, T any](
p Parser[In, T],
f func(T) Parser[In, Out],
@@ -33,7 +33,7 @@ func Bind[In, Out, T any](
return Fail[In, Out](anyConsumed, r2.Message()), nil
}
return Succeed(anyConsumed, val2, next, MessageOK(s.Pos())), nil
return Succeed(anyConsumed, val2, next, s.MessageOK()), nil
}
}
@@ -77,7 +77,7 @@ func Bind2[In, Out, T, T2 any](
return Fail[In, Out](anyConsumed, r3.Message()), nil
}
return Succeed(anyConsumed, val3, next, MessageOK(s.Pos())), nil
return Succeed(anyConsumed, val3, next, s.MessageOK()), nil
}
}
@@ -132,7 +132,7 @@ func Bind3[In, Out, T, T2, T3 any](
return Fail[In, Out](anyConsumed, r4.Message()), nil
}
return Succeed(anyConsumed, val4, next, MessageOK(s.Pos())), nil
return Succeed(anyConsumed, val4, next, s.MessageOK()), nil
}
}
@@ -198,7 +198,7 @@ func Bind4[In, Out, T, T2, T3, T4 any](
return Fail[In, Out](anyConsumed, r5.Message()), nil
}
return Succeed(anyConsumed, val5, next, MessageOK(s.Pos())), nil
return Succeed(anyConsumed, val5, next, s.MessageOK()), nil
}
}
@@ -275,6 +275,424 @@ func Bind5[In, Out, T, T2, T3, T4, T5 any](
return Fail[In, Out](anyConsumed, r6.Message()), nil
}
return Succeed(anyConsumed, val6, next, MessageOK(s.Pos())), nil
return Succeed(anyConsumed, val6, next, s.MessageOK()), nil
}
}
// Bind6 is equivalent to 6 nested calls to Bind.
func Bind6[In, Out, T, T2, T3, T4, T5, T6 any](
p Parser[In, T],
f func(T) Parser[In, T2],
f2 func(T2) Parser[In, T3],
f3 func(T3) Parser[In, T4],
f4 func(T4) Parser[In, T5],
f5 func(T5) Parser[In, T6],
f6 func(T6) Parser[In, Out],
) Parser[In, Out] {
return func(s State[In]) (Result[In, Out], error) {
var anyConsumed bool
var next = s
r, err := p(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r.Consumed()
success, val, next := r.Status()
if !success {
return Fail[In, Out](anyConsumed, r.Message()), nil
}
r2, err := f(val)(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r2.Consumed()
success, val2, next := r2.Status()
if !success {
return Fail[In, Out](anyConsumed, r2.Message()), nil
}
r3, err := f2(val2)(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r3.Consumed()
success, val3, next := r3.Status()
if !success {
return Fail[In, Out](anyConsumed, r3.Message()), nil
}
r4, err := f3(val3)(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r4.Consumed()
success, val4, next := r4.Status()
if !success {
return Fail[In, Out](anyConsumed, r4.Message()), nil
}
r5, err := f4(val4)(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r5.Consumed()
success, val5, next := r5.Status()
if !success {
return Fail[In, Out](anyConsumed, r5.Message()), nil
}
r6, err := f5(val5)(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r6.Consumed()
success, val6, next := r6.Status()
if !success {
return Fail[In, Out](anyConsumed, r6.Message()), nil
}
r7, err := f6(val6)(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r7.Consumed()
success, val7, next := r7.Status()
if !success {
return Fail[In, Out](anyConsumed, r7.Message()), nil
}
return Succeed(anyConsumed, val7, next, s.MessageOK()), nil
}
}
// Bind7 is equivalent to 7 nested calls to Bind.
func Bind7[In, Out, T, T2, T3, T4, T5, T6, T7 any](
p Parser[In, T],
f func(T) Parser[In, T2],
f2 func(T2) Parser[In, T3],
f3 func(T3) Parser[In, T4],
f4 func(T4) Parser[In, T5],
f5 func(T5) Parser[In, T6],
f6 func(T6) Parser[In, T7],
f7 func(T7) Parser[In, Out],
) Parser[In, Out] {
return func(s State[In]) (Result[In, Out], error) {
var anyConsumed bool
var next = s
r, err := p(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r.Consumed()
success, val, next := r.Status()
if !success {
return Fail[In, Out](anyConsumed, r.Message()), nil
}
r2, err := f(val)(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r2.Consumed()
success, val2, next := r2.Status()
if !success {
return Fail[In, Out](anyConsumed, r2.Message()), nil
}
r3, err := f2(val2)(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r3.Consumed()
success, val3, next := r3.Status()
if !success {
return Fail[In, Out](anyConsumed, r3.Message()), nil
}
r4, err := f3(val3)(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r4.Consumed()
success, val4, next := r4.Status()
if !success {
return Fail[In, Out](anyConsumed, r4.Message()), nil
}
r5, err := f4(val4)(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r5.Consumed()
success, val5, next := r5.Status()
if !success {
return Fail[In, Out](anyConsumed, r5.Message()), nil
}
r6, err := f5(val5)(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r6.Consumed()
success, val6, next := r6.Status()
if !success {
return Fail[In, Out](anyConsumed, r6.Message()), nil
}
r7, err := f6(val6)(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r7.Consumed()
success, val7, next := r7.Status()
if !success {
return Fail[In, Out](anyConsumed, r7.Message()), nil
}
r8, err := f7(val7)(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r8.Consumed()
success, val8, next := r8.Status()
if !success {
return Fail[In, Out](anyConsumed, r8.Message()), nil
}
return Succeed(anyConsumed, val8, next, s.MessageOK()), nil
}
}
// Bind8 is equivalent to 8 nested calls to Bind.
func Bind8[In, Out, T, T2, T3, T4, T5, T6, T7, T8 any](
p Parser[In, T],
f func(T) Parser[In, T2],
f2 func(T2) Parser[In, T3],
f3 func(T3) Parser[In, T4],
f4 func(T4) Parser[In, T5],
f5 func(T5) Parser[In, T6],
f6 func(T6) Parser[In, T7],
f7 func(T7) Parser[In, T8],
f8 func(T8) Parser[In, Out],
) Parser[In, Out] {
return func(s State[In]) (Result[In, Out], error) {
var anyConsumed bool
var next = s
r, err := p(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r.Consumed()
success, val, next := r.Status()
if !success {
return Fail[In, Out](anyConsumed, r.Message()), nil
}
r2, err := f(val)(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r2.Consumed()
success, val2, next := r2.Status()
if !success {
return Fail[In, Out](anyConsumed, r2.Message()), nil
}
r3, err := f2(val2)(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r3.Consumed()
success, val3, next := r3.Status()
if !success {
return Fail[In, Out](anyConsumed, r3.Message()), nil
}
r4, err := f3(val3)(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r4.Consumed()
success, val4, next := r4.Status()
if !success {
return Fail[In, Out](anyConsumed, r4.Message()), nil
}
r5, err := f4(val4)(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r5.Consumed()
success, val5, next := r5.Status()
if !success {
return Fail[In, Out](anyConsumed, r5.Message()), nil
}
r6, err := f5(val5)(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r6.Consumed()
success, val6, next := r6.Status()
if !success {
return Fail[In, Out](anyConsumed, r6.Message()), nil
}
r7, err := f6(val6)(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r7.Consumed()
success, val7, next := r7.Status()
if !success {
return Fail[In, Out](anyConsumed, r7.Message()), nil
}
r8, err := f7(val7)(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r8.Consumed()
success, val8, next := r8.Status()
if !success {
return Fail[In, Out](anyConsumed, r8.Message()), nil
}
r9, err := f8(val8)(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r9.Consumed()
success, val9, next := r9.Status()
if !success {
return Fail[In, Out](anyConsumed, r9.Message()), nil
}
return Succeed(anyConsumed, val9, next, s.MessageOK()), nil
}
}
// Bind9 is equivalent to 9 nested calls to Bind.
func Bind9[In, Out, T, T2, T3, T4, T5, T6, T7, T8, T9 any](
p Parser[In, T],
f func(T) Parser[In, T2],
f2 func(T2) Parser[In, T3],
f3 func(T3) Parser[In, T4],
f4 func(T4) Parser[In, T5],
f5 func(T5) Parser[In, T6],
f6 func(T6) Parser[In, T7],
f7 func(T7) Parser[In, T8],
f8 func(T8) Parser[In, T9],
f9 func(T9) Parser[In, Out],
) Parser[In, Out] {
return func(s State[In]) (Result[In, Out], error) {
var anyConsumed bool
var next = s
r, err := p(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r.Consumed()
success, val, next := r.Status()
if !success {
return Fail[In, Out](anyConsumed, r.Message()), nil
}
r2, err := f(val)(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r2.Consumed()
success, val2, next := r2.Status()
if !success {
return Fail[In, Out](anyConsumed, r2.Message()), nil
}
r3, err := f2(val2)(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r3.Consumed()
success, val3, next := r3.Status()
if !success {
return Fail[In, Out](anyConsumed, r3.Message()), nil
}
r4, err := f3(val3)(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r4.Consumed()
success, val4, next := r4.Status()
if !success {
return Fail[In, Out](anyConsumed, r4.Message()), nil
}
r5, err := f4(val4)(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r5.Consumed()
success, val5, next := r5.Status()
if !success {
return Fail[In, Out](anyConsumed, r5.Message()), nil
}
r6, err := f5(val5)(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r6.Consumed()
success, val6, next := r6.Status()
if !success {
return Fail[In, Out](anyConsumed, r6.Message()), nil
}
r7, err := f6(val6)(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r7.Consumed()
success, val7, next := r7.Status()
if !success {
return Fail[In, Out](anyConsumed, r7.Message()), nil
}
r8, err := f7(val7)(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r8.Consumed()
success, val8, next := r8.Status()
if !success {
return Fail[In, Out](anyConsumed, r8.Message()), nil
}
r9, err := f8(val8)(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r9.Consumed()
success, val9, next := r9.Status()
if !success {
return Fail[In, Out](anyConsumed, r9.Message()), nil
}
r10, err := f9(val9)(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r10.Consumed()
success, val10, next := r10.Status()
if !success {
return Fail[In, Out](anyConsumed, r10.Message()), nil
}
return Succeed(anyConsumed, val10, next, s.MessageOK()), nil
}
}
+29
View File
@@ -0,0 +1,29 @@
// SPDX-License-Identifier: Unlicense
package bytes
import (
"git.codemonkeysoftware.net/b/gigaparsec"
)
// Token wraps a parser to discard all whitespace after the parsed input.
// whitespace is a parser that will consume non-empty whitespace.
func Token[Out any](whitespace gigaparsec.Parser[byte, struct{}]) func(p gigaparsec.Parser[byte, Out]) gigaparsec.Parser[byte, Out] {
mappedWS := whitespace.Map(func(struct{}) struct{} { return struct{}{} })
var ignoreWS gigaparsec.Parser[byte, struct{}] = func(s gigaparsec.State[byte]) (gigaparsec.Result[byte, struct{}], error) {
result, err := mappedWS(s)
return result.Consume(false), err
}
return func(p gigaparsec.Parser[byte, Out]) gigaparsec.Parser[byte, Out] {
return gigaparsec.Seq(p, gigaparsec.Repeat(0, ignoreWS), func(val Out, _ []struct{}) Out { return val })
}
}
func isWhitespaceASCII(b byte) bool {
return b == ' ' || (b >= '\t' && b <= '\r')
}
// WhitespaceASCII consumes one ASCII whitespace character: space, tab, LF, CR, vertical tab, or form feed.
func WhitespaceASCII() gigaparsec.Parser[byte, struct{}] {
return gigaparsec.Satisfy(isWhitespaceASCII).Map(func(byte) struct{} { return struct{}{} })
}
+39
View File
@@ -0,0 +1,39 @@
// SPDX-License-Identifier: Unlicense
package bytes_test
import (
"fmt"
"strings"
"git.codemonkeysoftware.net/b/gigaparsec"
"git.codemonkeysoftware.net/b/gigaparsec/bytes"
)
func Example() {
ptokens := gigaparsec.Choose(
bytes.MatchString("("),
bytes.MatchString(")"),
bytes.MatchString("+"),
bytes.MatchString("-"),
bytes.Regexp(`[a-zA-Z][a-zA-Z0-9_]*`).Label("identifier"),
bytes.Regexp(`[0-9]+`).Label("number"),
)
tokenize := gigaparsec.Repeat(0, bytes.Token[string](bytes.WhitespaceASCII())(ptokens))
const src = `(alpha
+ 42 ) -5 `
tokens, _ := tokenize.Run(strings.NewReader(src))
for _, token := range tokens {
fmt.Println(token)
}
// Output:
// (
// alpha
// +
// 42
// )
// -
// 5
}
+9 -6
View File
@@ -69,22 +69,25 @@ func Regexp(pattern string) gigaparsec.Parser[byte, string] {
return gigaparsec.Result[byte, string]{}, fmt.Errorf("Regexp: reader error: %w", err)
}
if idx == nil {
if err == io.EOF {
return gigaparsec.Fail[byte, string](false, input.MessageOK()), nil
}
got := make([]byte, r.Count())
_, _, err = input.Read(got)
if err != nil {
return gigaparsec.Result[byte, string]{}, fmt.Errorf("Regexp: unexpected error: %w", err)
}
return gigaparsec.Fail[byte, string](false, gigaparsec.MakeMessage(input.Pos(), string(got), expected)), nil
return gigaparsec.Fail[byte, string](false, input.Message(string(got), expected)), nil
}
// Alas, this is a little wasteful because a Regexp can only return indices
// when searching a RuneReader.
dst := make([]byte, idx[1]-idx[0])
n, _, err := input.Read(dst)
if err != nil {
if err != nil && (!errors.Is(err, io.EOF) || n < uint64(len(dst))) {
return gigaparsec.Result[byte, string]{}, fmt.Errorf("Regexp: unexpected error: %w", err)
}
next := input.At(input.Pos() + n)
return gigaparsec.Succeed(true, string(dst), next, gigaparsec.MessageOK(input.Pos())), nil
return gigaparsec.Succeed(true, string(dst), next, input.MessageOK()), nil
}
}
@@ -95,14 +98,14 @@ func MatchString(s string) gigaparsec.Parser[byte, string] {
dst := make([]byte, len(s))
_, next, err := input.Read(dst)
if errors.Is(err, io.EOF) {
return gigaparsec.Fail[byte, string](false, gigaparsec.MessageEnd(input.Pos(), expected)), nil
return gigaparsec.Fail[byte, string](false, input.MessageEnd(expected)), nil
}
if err != nil {
return gigaparsec.Result[byte, string]{}, fmt.Errorf("MatchString: %w", err)
}
if !bytes.Equal(dst, b) {
return gigaparsec.Fail[byte, string](false, gigaparsec.MakeMessage(input.Pos(), string(dst), expected)), nil
return gigaparsec.Fail[byte, string](false, input.Message(string(dst), expected)), nil
}
return gigaparsec.Succeed(true, s, next, gigaparsec.MessageOK(input.Pos())), nil
return gigaparsec.Succeed(true, s, next, input.MessageOK()), nil
}
}
+56 -15
View File
@@ -4,6 +4,7 @@ package bytes_test
import (
"bytes"
"errors"
"strings"
"testing"
@@ -16,24 +17,64 @@ import (
"pgregory.net/rapid"
)
func Todo(t *testing.T) {
t.Fatalf("TODO")
}
func TestRegexp(t *testing.T) {
t.Run("only searches the beginning of input", Todo)
t.Run("position is correct after match", Todo)
t.Run("fails on unexpected error", Todo)
t.Run("returns a useful Got value", rapid.MakeCheck(func(t *rapid.T) {
alpha := rapid.SliceOfBytesMatching(`[A-Za-z]{1,100}`)
t.Run("position and value are correct after match", rapid.MakeCheck(func(t *rapid.T) {
needle := alpha.Draw(t, "needle")
input := rapid.Map(alpha, func(suffix []byte) []byte { return append(needle, suffix...) }).
Draw(t, "input")
p := pbytes.Regexp(string(needle))
result, err := p.RunToResult(bytes.NewReader(input))
succeeded, val, next := result.Status()
}))
t.Run("basically works", func(t *testing.T) {
result, err := pbytes.Regexp("a")(gigaparsec.MakeState(strings.NewReader("a")))
must.NoError(t, err)
success, value, _ := result.Status()
test.True(t, success, test.Sprint(result.Message()))
test.EqOp(t, "a", value)
test.True(t, result.Consumed())
test.True(t, succeeded)
test.EqOp(t, string(needle), val)
ptest.StateIsAt(t, next, uint64(len(needle)))
}))
t.Run("only searches the beginning of input", rapid.MakeCheck(func(t *rapid.T) {
needle := alpha.Draw(t, "needle")
input := rapid.Map(alpha, func(prefix []byte) []byte { return append(prefix, needle...) }).
Filter(func(b []byte) bool { return !bytes.HasPrefix(b, needle) }).
Draw(t, "input")
p := pbytes.Regexp(string(needle))
result, err := p.RunToResult(bytes.NewReader(input))
succeeded, _, _ := result.Status()
must.NoError(t, err)
test.False(t, succeeded)
}))
t.Run("fails on unexpected error", func(t *testing.T) {
expectedErr := errors.New("it broke")
p := pbytes.Regexp("nope")
result, err := p.RunToResult(ptest.ErrReaderAt(expectedErr))
succeeded, _, _ := result.Status()
test.ErrorIs(t, err, expectedErr)
test.False(t, succeeded)
})
t.Run("returns a useful Got value", func(t *testing.T) {
p := pbytes.Regexp("hello")
result, err := p.RunToResult(strings.NewReader("hellaparsec"))
must.NoError(t, err)
test.StrContains(t, result.Message().Got(), "hella")
})
t.Run("succeeds on empty matches", func(t *testing.T) {
p := pbytes.Regexp(".*")
result, err := p.RunToResult(strings.NewReader(""))
succeeded, value, _ := result.Status()
must.NoError(t, err)
must.True(t, succeeded)
must.EqOp(t, "", value)
})
t.Run("fails without an error at EOF", func(t *testing.T) {
p := pbytes.Regexp("a")
result, err := p.RunToResult(strings.NewReader(""))
succeeded, _, _ := result.Status()
must.NoError(t, err)
must.False(t, succeeded)
})
}
+106 -60
View File
@@ -2,7 +2,7 @@
package gigaparsec
//go:generate go run ./internal/bindgen -bindpath bind.go -seqpath seq.go -max 5 -pkg gigaparsec
//go:generate go run ./internal/bindgen -bindpath bind.go -seqpath seq.go -max 9 -pkg gigaparsec
import (
"errors"
"fmt"
@@ -59,12 +59,24 @@ func (r Result[In, Out]) Message() Message {
return r.message
}
func MakeMessage(pos uint64, got string, expected ...string) Message {
return Message{
pos: pos,
got: got,
expected: expected,
type ReaderAt[T any] interface {
ReadAt(p []T, off int64) (n int, err error)
}
type SliceReaderAt[T any] []T
func (s SliceReaderAt[T]) ReadAt(dst []T, off int64) (n int, err error) {
if off < 0 {
return 0, errors.New("SliceReaderAt.ReadAt: negative offset")
}
if off >= int64(len(s)) {
return 0, io.EOF
}
n = copy(dst, s[off:])
if n < len(dst) {
err = io.EOF
}
return n, err
}
type Message struct {
@@ -107,47 +119,38 @@ func (m Message) String() string {
return s
}
func MakeMessage(pos uint64, got string, expected ...string) Message {
return Message{
pos: pos,
got: got,
expected: expected,
}
}
func MessageOK(pos uint64) Message { return Message{pos: pos} }
func MessageEnd(pos uint64, expected ...string) Message {
return Message{pos: pos, got: "end of input", expected: expected}
}
type ReaderAt[T any] interface {
ReadAt(p []T, off int64) (n int, err error)
}
type SliceReaderAt[T any] []T
func (s SliceReaderAt[T]) ReadAt(dst []T, off int64) (n int, err error) {
if off < 0 {
return 0, errors.New("SliceReaderAt.ReadAt: negative offset")
}
if off >= int64(len(s)) {
return 0, io.EOF
}
n = copy(dst, s[off:])
if n < len(dst) {
err = io.EOF
}
return n, err
}
// MakeState creates a new State.
// Its mostly useful for testing. You probably dont need it.
func MakeState[In any](r ReaderAt[In]) State[In] {
return State[In]{r: r}
}
// State represents a position in an input source.
type State[In any] struct {
r ReaderAt[In]
pos uint64
}
// Read fills dst with data from this State's position in the underlying source.
// Read fills dst with data from this States position in the underlying source.
// It returns the number of data it read and a new State for the position at which
// the read ended, and an error if the read either (1) failed or (2) reached the
// end of the source before filling dst. All reads from a given State will return
// data from the same position the source.
// If the source had too few data left to fill dst, or if the State's position is
// If the source had too few data left to fill dst, or if the States position is
// at or past the end of the source, err will be io.EOF.
func (s State[In]) Read(dst []In) (n uint64, next State[In], err error) {
if s.pos > math.MaxInt64 {
@@ -166,7 +169,7 @@ func (s State[In]) Read(dst []In) (n uint64, next State[In], err error) {
return uint64(nread), s, err
}
// Pos returns this State's position.
// Pos returns this States position.
func (s State[In]) Pos() uint64 {
return s.pos
}
@@ -176,6 +179,16 @@ func (s State[In]) At(pos uint64) State[In] {
return State[In]{r: s.r, pos: pos}
}
func (s State[In]) Message(got string, expected ...string) Message {
return MakeMessage(s.pos, got, expected...)
}
func (s State[In]) MessageOK() Message { return MessageOK(s.pos) }
func (s State[in]) MessageEnd(expected ...string) Message {
return MessageEnd(s.pos, expected...)
}
type Parser[In, Out any] func(State[In]) (Result[In, Out], error)
// Label creates a parser identical to p, except that a failed result will
@@ -194,15 +207,34 @@ func (p Parser[In, Out]) Label(label string) Parser[In, Out] {
}
}
type ParseError Message
func (pe ParseError) Error() string {
return Message(pe).String()
// Where creates a parser that fails if pred does not hold.
func (p Parser[In, Out]) Where(pred func(Out) bool) Parser[In, Out] {
return func(s State[In]) (Result[In, Out], error) {
result, err := p(s)
if result.success && !pred(result.value) {
result.success = false
result.message.got = "failed Where predicate"
}
return result, err
}
}
func Run[In, Out any](p Parser[In, Out], r ReaderAt[In]) (out Out, err error) {
// Map creates a parser that converts the output of p from Out1 to Out2.
func (p Parser[In, Out]) Map[Out2 any](f func(Out) Out2) Parser[In, Out2] {
return Bind(p, func(out Out) Parser[In, Out2] {
return Return[In](f(out))
})
}
// RunToResult executes a parser on r and returns the result, or an error if reading the inputs fails.
func (p Parser[In, Out]) RunToResult(r ReaderAt[In]) (Result[In, Out], error) {
start := MakeState(r)
result, err := p(start)
return p(start)
}
// Run executes a parser on r and returns the output, or an an error if the parse fails.
func (p Parser[In, Out]) Run(r ReaderAt[In]) (out Out, err error) {
result, err := p.RunToResult(r)
if err != nil {
err = fmt.Errorf("Run: %w", err)
return
@@ -215,10 +247,16 @@ func Run[In, Out any](p Parser[In, Out], r ReaderAt[In]) (out Out, err error) {
return
}
type ParseError Message
func (pe ParseError) Error() string {
return Message(pe).String()
}
// Return creates a parser that always succeeds and returns value without consuming any input.
func Return[In, Out any](value Out) Parser[In, Out] {
return func(state State[In]) (Result[In, Out], error) {
return Succeed(false, value, state, MessageOK(state.Pos())), nil
return Succeed(false, value, state, state.MessageOK()), nil
}
}
@@ -229,7 +267,7 @@ func Satisfy[T any](pred func(T) bool) Parser[T, T] {
token := make([]T, 1)
n, next, err := state.Read(token)
if errors.Is(err, io.EOF) {
return Fail[T, T](false, MessageEnd(state.Pos())), nil
return Fail[T, T](false, state.MessageEnd()), nil
}
if err != nil {
return Result[T, T]{}, err
@@ -238,9 +276,9 @@ func Satisfy[T any](pred func(T) bool) Parser[T, T] {
panic(fmt.Sprintf("expected 1 element from Read, but got %d", n))
}
if pred(token[0]) {
return Succeed(true, token[0], next, MessageOK(state.Pos())), nil
return Succeed(true, token[0], next, state.MessageOK()), nil
}
return Fail[T, T](false, MakeMessage(state.Pos(), fmt.Sprint(token))), nil
return Fail[T, T](false, state.Message(fmt.Sprint(token))), nil
}
}
@@ -252,15 +290,15 @@ func Match[T comparable](x T) Parser[T, T] {
token := make([]T, 1)
_, next, err := state.Read(token)
if errors.Is(err, io.EOF) {
return Fail[T, T](false, MessageEnd(state.Pos())), nil
return Fail[T, T](false, state.MessageEnd()), nil
}
if err != nil {
return Result[T, T]{}, err
}
if token[0] == x {
return Succeed(true, token[0], next, MessageOK(state.Pos())), nil
return Succeed(true, token[0], next, state.MessageOK()), nil
}
return Fail[T, T](false, MakeMessage(state.Pos(), fmt.Sprint(token), expected)), nil
return Fail[T, T](false, state.Message(fmt.Sprint(token), expected)), nil
}
}
@@ -272,15 +310,15 @@ func MatchSlice[T comparable](s []T) Parser[T, []T] {
token := make([]T, len(s))
_, next, err := state.Read(token)
if errors.Is(err, io.EOF) {
return Fail[T, []T](false, MessageEnd(state.Pos())), nil
return Fail[T, []T](false, state.MessageEnd()), nil
}
if err != nil {
return Result[T, []T]{}, err
}
if !slices.Equal(s, token) {
return Fail[T, []T](false, MakeMessage(state.Pos(), fmt.Sprint(token), expected)), nil
return Fail[T, []T](false, state.Message(fmt.Sprint(token), expected)), nil
}
return Succeed(true, token, next, MessageOK(state.Pos())), nil
return Succeed(true, token, next, state.MessageOK()), nil
}
}
@@ -317,7 +355,7 @@ func Choose[In, Out any](p Parser[In, Out], ps ...Parser[In, Out]) Parser[In, Ou
got = qMsg.got
}
}
msg := MakeMessage(input.Pos(), got, slices.Concat(expecteds...)...)
msg := input.Message(got, slices.Concat(expecteds...)...)
if failed {
return Fail[In, Out](false, msg), nil
}
@@ -344,22 +382,15 @@ func Try[In, Out any](p Parser[In, Out]) Parser[In, Out] {
}
}
// Map creates a parser that converts the output of p from Out1 to Out2.
func Map[In, Out1, Out2 any](p Parser[In, Out1], f func(Out1) Out2) Parser[In, Out2] {
return Bind(p, func(out Out1) Parser[In, Out2] {
return Return[In](f(out))
})
}
func end[In any](s State[In]) (Result[In, struct{}], error) {
_, _, err := s.Read([]In{})
if errors.Is(err, io.EOF) {
return Succeed(true, struct{}{}, s, MessageOK(s.Pos())), nil
return Succeed(true, struct{}{}, s, s.MessageOK()), nil
}
if err != nil {
return Result[In, struct{}]{}, fmt.Errorf("End: unexpected error: %w", err)
}
return Fail[In, struct{}](false, MakeMessage(s.Pos(), "", "end of input")), nil
return Fail[In, struct{}](false, s.Message("", "end of input")), nil
}
// End creates a parser that succeeds at the end of the input and fails otherwise.
@@ -379,26 +410,41 @@ 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, state.MessageOK()), nil
}
return Fail[In, []Out](consumed, result.Message()), nil
}
currState = nextState
values = append(values, value)
}
}
}
// Lazy delays creating a parser from p until the parser is called.
// This is useful for preventing recursive function calls in the
// definition of a recursive parser.
func Lazy[In, Out any](p func() Parser[In, Out]) Parser[In, Out] {
return func(s State[In]) (Result[In, Out], error) {
return p()(s)
}
}
// Bracket parses p, flanked by left and right.
func Bracket[In, Out, LOut, ROut any](left Parser[In, LOut], p Parser[In, Out], right Parser[In, ROut]) Parser[In, Out] {
return Seq3(left, p, right, func(_ LOut, val Out, _ ROut) Out { return val })
}
+1 -1
View File
@@ -1,6 +1,6 @@
module git.codemonkeysoftware.net/b/gigaparsec
go 1.23
go 1.27
require (
github.com/shoenig/test v1.11.0
+5 -5
View File
@@ -1,19 +1,19 @@
- Hutton & Meijer, _[Monadic Parser Combinators](http://www.cs.nott.ac.uk/~pszgmh/monparsing.pdf)_
The original paper on monadic parser combinators.
The original paper on monadic parser combinators.
- Leijen & Meijer, _[Parsec: Direct Style Monadic Parser Combinators For The Real World](https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/parsec-paper-letter.pdf)_
Describes Parsec, a monadic parser combinator library that limits backtracking by default and adds better error messages.
Describes Parsec, a monadic parser combinator library that limits backtracking by default and adds better error messages.
- [Megaparsec](https://hackage.haskell.org/package/megaparsec)
Improved fork of Megaparsec.
Improved fork of Megaparsec.
- [FParsec](https://github.com/stephan-tolksdorf/fparsec)
Parsec implementation in F#.
Parsec implementation in F#.
- [Sprache](https://github.com/sprache/Sprache)
Parsec implementation in C#.
Parsec implementation in C#.
+2 -2
View File
@@ -5,7 +5,7 @@
{{end}}{{end}}{{end -}}
{{define "func" -}}
{{if eq . 1}}// Bind combines p with a parser created by f that depends on p's result value.
{{if eq . 1}}// Bind combines p with a parser created by f that depends on ps result value.
{{- else}}// Bind{{.}} is equivalent to {{.}} nested calls to Bind.{{end}}
func Bind{{.}}[In, Out{{range .Count}}, T{{.}}{{end}} any](
p Parser[In, T],
@@ -24,7 +24,7 @@ func Bind{{.}}[In, Out{{range .Count}}, T{{.}}{{end}} any](
return Fail[In, Out](anyConsumed, r{{.}}.Message()), nil
}
{{end}}
return Succeed(anyConsumed, val{{.Next}}, next, MessageOK(s.Pos())), nil
return Succeed(anyConsumed, val{{.Next}}, next, s.MessageOK()), nil
}
}{{end -}}
+3 -2
View File
@@ -1,7 +1,8 @@
{{/* SPDX-License-Identifier: Unlicense */ -}}
{{define "func"}}{{if gt . 1 -}}
func Seq{{.}}[In, Out{{range .Count}}, T{{.}}{{end}} any](
// Seq{{if gt . 2}}{{.}}{{end}} runs {{.}} parsers in sequence and maps their collected results with f.
func Seq{{if gt . 2}}{{.}}{{end}}[In, Out{{range .Count}}, T{{.}}{{end}} any](
{{- range .Count}}
p{{.}} Parser[In, T{{.}}],{{end}}
f func({{range .Count}}{{if ne . 1}}, {{end}}T{{.}}{{end}}) Out,
@@ -21,7 +22,7 @@ func Seq{{.}}[In, Out{{range .Count}}, T{{.}}{{end}} any](
}
{{end}}
final := f({{range .Count}}{{if ne . 1}}, {{end}}val{{.}}{{end}})
return Succeed(anyConsumed, final, next, MessageOK(s.Pos())), nil
return Succeed(anyConsumed, final, next, s.MessageOK()), nil
}
}{{end}}{{end -}}
+265
View File
@@ -0,0 +1,265 @@
// SPDX-License-Identifier: Unlicense
// Package naive contains naïve implementations of the Bind and Seq combinators.
// The accompanying tests include simple benchmarks comparing their performance.
package naive
import gp "git.codemonkeysoftware.net/b/gigaparsec"
func Bind2[In, Out, T, T2 any](
p gp.Parser[In, T],
f func(T) gp.Parser[In, T2],
f2 func(T2) gp.Parser[In, Out],
) gp.Parser[In, Out] {
return gp.Bind(gp.Bind(p, f), f2)
}
func Bind3[In, Out, T, T2, T3, T4, T5, T6, T7, T8, T9 any](
p gp.Parser[In, T],
f func(T) gp.Parser[In, T2],
f2 func(T2) gp.Parser[In, T3],
f3 func(T3) gp.Parser[In, Out],
) gp.Parser[In, Out] {
return gp.Bind(gp.Bind(gp.Bind(p, f), f2), f3)
}
func Bind4[In, Out, T, T2, T3, T4 any](
p gp.Parser[In, T],
f func(T) gp.Parser[In, T2],
f2 func(T2) gp.Parser[In, T3],
f3 func(T3) gp.Parser[In, T4],
f4 func(T4) gp.Parser[In, Out],
) gp.Parser[In, Out] {
return gp.Bind(gp.Bind(gp.Bind(gp.Bind(p, f), f2), f3), f4)
}
func Bind5[In, Out, T, T2, T3, T4, T5 any](
p gp.Parser[In, T],
f func(T) gp.Parser[In, T2],
f2 func(T2) gp.Parser[In, T3],
f3 func(T3) gp.Parser[In, T4],
f4 func(T4) gp.Parser[In, T5],
f5 func(T5) gp.Parser[In, Out],
) gp.Parser[In, Out] {
return gp.Bind(gp.Bind(gp.Bind(gp.Bind(gp.Bind(p, f), f2), f3), f4), f5)
}
func Bind6[In, Out, T, T2, T3, T4, T5, T6 any](
p gp.Parser[In, T],
f func(T) gp.Parser[In, T2],
f2 func(T2) gp.Parser[In, T3],
f3 func(T3) gp.Parser[In, T4],
f4 func(T4) gp.Parser[In, T5],
f5 func(T5) gp.Parser[In, T6],
f6 func(T6) gp.Parser[In, Out],
) gp.Parser[In, Out] {
return gp.Bind(gp.Bind(gp.Bind(gp.Bind(gp.Bind(gp.Bind(p, f), f2), f3), f4), f5), f6)
}
func Bind7[In, Out, T, T2, T3, T4, T5, T6, T7 any](
p gp.Parser[In, T],
f func(T) gp.Parser[In, T2],
f2 func(T2) gp.Parser[In, T3],
f3 func(T3) gp.Parser[In, T4],
f4 func(T4) gp.Parser[In, T5],
f5 func(T5) gp.Parser[In, T6],
f6 func(T6) gp.Parser[In, T7],
f7 func(T7) gp.Parser[In, Out],
) gp.Parser[In, Out] {
return gp.Bind(gp.Bind(gp.Bind(gp.Bind(gp.Bind(gp.Bind(gp.Bind(p, f), f2), f3), f4), f5), f6), f7)
}
func Bind8[In, Out, T, T2, T3, T4, T5, T6, T7, T8 any](
p gp.Parser[In, T],
f func(T) gp.Parser[In, T2],
f2 func(T2) gp.Parser[In, T3],
f3 func(T3) gp.Parser[In, T4],
f4 func(T4) gp.Parser[In, T5],
f5 func(T5) gp.Parser[In, T6],
f6 func(T6) gp.Parser[In, T7],
f7 func(T7) gp.Parser[In, T8],
f8 func(T8) gp.Parser[In, Out],
) gp.Parser[In, Out] {
return gp.Bind(gp.Bind(gp.Bind(gp.Bind(gp.Bind(gp.Bind(gp.Bind(gp.Bind(p, f), f2), f3), f4), f5), f6), f7), f8)
}
func Bind9[In, Out, T, T2, T3, T4, T5, T6, T7, T8, T9 any](
p gp.Parser[In, T],
f func(T) gp.Parser[In, T2],
f2 func(T2) gp.Parser[In, T3],
f3 func(T3) gp.Parser[In, T4],
f4 func(T4) gp.Parser[In, T5],
f5 func(T5) gp.Parser[In, T6],
f6 func(T6) gp.Parser[In, T7],
f7 func(T7) gp.Parser[In, T8],
f8 func(T8) gp.Parser[In, T9],
f9 func(T9) gp.Parser[In, Out],
) gp.Parser[In, Out] {
return gp.Bind(gp.Bind(gp.Bind(gp.Bind(gp.Bind(gp.Bind(gp.Bind(gp.Bind(gp.Bind(p, f), f2), f3), f4), f5), f6), f7), f8), f9)
}
func Seq[In, Out, T, T2 any](
p gp.Parser[In, T],
p2 gp.Parser[In, T2],
f func(T, T2) Out,
) gp.Parser[In, Out] {
return gp.Bind(p, func(x T) gp.Parser[In, Out] {
return gp.Bind(p2, func(x2 T2) gp.Parser[In, Out] {
return gp.Return[In](f(x, x2))
})
})
}
func Seq3[In, Out, T, T2, T3 any](
p gp.Parser[In, T],
p2 gp.Parser[In, T2],
p3 gp.Parser[In, T3],
f func(T, T2, T3) Out,
) gp.Parser[In, Out] {
return gp.Bind(p, func(x T) gp.Parser[In, Out] {
return gp.Bind(p2, func(x2 T2) gp.Parser[In, Out] {
return gp.Bind(p3, func(x3 T3) gp.Parser[In, Out] {
return gp.Return[In](f(x, x2, x3))
})
})
})
}
func Seq4[In, Out, T, T2, T3, T4 any](
p gp.Parser[In, T],
p2 gp.Parser[In, T2],
p3 gp.Parser[In, T3],
p4 gp.Parser[In, T4],
f func(T, T2, T3, T4) Out,
) gp.Parser[In, Out] {
return gp.Bind(p, func(x T) gp.Parser[In, Out] {
return gp.Bind(p2, func(x2 T2) gp.Parser[In, Out] {
return gp.Bind(p3, func(x3 T3) gp.Parser[In, Out] {
return gp.Bind(p4, func(x4 T4) gp.Parser[In, Out] {
return gp.Return[In](f(x, x2, x3, x4))
})
})
})
})
}
func Seq5[In, Out, T, T2, T3, T4, T5 any](
p gp.Parser[In, T],
p2 gp.Parser[In, T2],
p3 gp.Parser[In, T3],
p4 gp.Parser[In, T4],
p5 gp.Parser[In, T5],
f func(T, T2, T3, T4, T5) Out,
) gp.Parser[In, Out] {
return gp.Bind(p, func(x T) gp.Parser[In, Out] {
return gp.Bind(p2, func(x2 T2) gp.Parser[In, Out] {
return gp.Bind(p3, func(x3 T3) gp.Parser[In, Out] {
return gp.Bind(p4, func(x4 T4) gp.Parser[In, Out] {
return gp.Bind(p5, func(x5 T5) gp.Parser[In, Out] {
return gp.Return[In](f(x, x2, x3, x4, x5))
})
})
})
})
})
}
func Seq6[In, Out, T, T2, T3, T4, T5, T6 any](
p gp.Parser[In, T],
p2 gp.Parser[In, T2],
p3 gp.Parser[In, T3],
p4 gp.Parser[In, T4],
p5 gp.Parser[In, T5],
p6 gp.Parser[In, T6],
f func(T, T2, T3, T4, T5, T6) Out,
) gp.Parser[In, Out] {
return gp.Bind(p, func(x T) gp.Parser[In, Out] {
return gp.Bind(p2, func(x2 T2) gp.Parser[In, Out] {
return gp.Bind(p3, func(x3 T3) gp.Parser[In, Out] {
return gp.Bind(p4, func(x4 T4) gp.Parser[In, Out] {
return gp.Bind(p5, func(x5 T5) gp.Parser[In, Out] {
return gp.Bind(p6, func(x6 T6) gp.Parser[In, Out] {
return gp.Return[In](f(x, x2, x3, x4, x5, x6))
})
})
})
})
})
})
}
func Seq7[In, Out, T, T2, T3, T4, T5, T6, T7 any](
p gp.Parser[In, T],
p2 gp.Parser[In, T2],
p3 gp.Parser[In, T3],
p4 gp.Parser[In, T4],
p5 gp.Parser[In, T5],
p6 gp.Parser[In, T6],
p7 gp.Parser[In, T7],
f func(T, T2, T3, T4, T5, T6, T7) Out,
) gp.Parser[In, Out] {
return gp.Bind(p, func(x T) gp.Parser[In, Out] {
return gp.Bind(p2, func(x2 T2) gp.Parser[In, Out] {
return gp.Bind(p3, func(x3 T3) gp.Parser[In, Out] {
return gp.Bind(p4, func(x4 T4) gp.Parser[In, Out] {
return gp.Bind(p5, func(x5 T5) gp.Parser[In, Out] {
return gp.Bind(p6, func(x6 T6) gp.Parser[In, Out] {
return gp.Bind(p7, func(x7 T7) gp.Parser[In, Out] {
return gp.Return[In](f(x, x2, x3, x4, x5, x6, x7))
})
})
})
})
})
})
})
}
func Seq8[In, Out, T, T2, T3, T4, T5, T6, T7, T8 any](
p gp.Parser[In, T],
p2 gp.Parser[In, T2],
p3 gp.Parser[In, T3],
p4 gp.Parser[In, T4],
p5 gp.Parser[In, T5],
p6 gp.Parser[In, T6],
p7 gp.Parser[In, T7],
p8 gp.Parser[In, T8],
f func(T, T2, T3, T4, T5, T6, T7, T8) Out,
) gp.Parser[In, Out] {
return gp.Bind(p, func(x T) gp.Parser[In, Out] {
return gp.Bind(p2, func(x2 T2) gp.Parser[In, Out] {
return gp.Bind(p3, func(x3 T3) gp.Parser[In, Out] {
return gp.Bind(p4, func(x4 T4) gp.Parser[In, Out] {
return gp.Bind(p5, func(x5 T5) gp.Parser[In, Out] {
return gp.Bind(p6, func(x6 T6) gp.Parser[In, Out] {
return gp.Bind(p7, func(x7 T7) gp.Parser[In, Out] {
return gp.Bind(p8, func(x8 T8) gp.Parser[In, Out] {
return gp.Return[In](f(x, x2, x3, x4, x5, x6, x7, x8))
})
})
})
})
})
})
})
})
}
func Seq9[In, Out, T, T2, T3, T4, T5, T6, T7, T8, T9 any](
p gp.Parser[In, T],
p2 gp.Parser[In, T2],
p3 gp.Parser[In, T3],
p4 gp.Parser[In, T4],
p5 gp.Parser[In, T5],
p6 gp.Parser[In, T6],
p7 gp.Parser[In, T7],
p8 gp.Parser[In, T8],
p9 gp.Parser[In, T9],
f func(T, T2, T3, T4, T5, T6, T7, T8, T9) Out,
) gp.Parser[In, Out] {
return gp.Bind(p, func(x T) gp.Parser[In, Out] {
return gp.Bind(p2, func(x2 T2) gp.Parser[In, Out] {
return gp.Bind(p3, func(x3 T3) gp.Parser[In, Out] {
return gp.Bind(p4, func(x4 T4) gp.Parser[In, Out] {
return gp.Bind(p5, func(x5 T5) gp.Parser[In, Out] {
return gp.Bind(p6, func(x6 T6) gp.Parser[In, Out] {
return gp.Bind(p7, func(x7 T7) gp.Parser[In, Out] {
return gp.Bind(p8, func(x8 T8) gp.Parser[In, Out] {
return gp.Bind(p9, func(x9 T9) gp.Parser[In, Out] {
return gp.Return[In](f(x, x2, x3, x4, x5, x6, x7, x8, x9))
})
})
})
})
})
})
})
})
})
}
+108
View File
@@ -0,0 +1,108 @@
// SPDX-License-Identifier: Unlicense
package naive_test
import (
"testing"
"git.codemonkeysoftware.net/b/gigaparsec"
"git.codemonkeysoftware.net/b/gigaparsec/naive"
)
func BenchmarkBind5(b *testing.B) {
type Bind5T = func(p gigaparsec.Parser[byte, byte], f func(byte) gigaparsec.Parser[byte, byte], f2 func(byte) gigaparsec.Parser[byte, byte], f3 func(byte) gigaparsec.Parser[byte, byte], f4 func(byte) gigaparsec.Parser[byte, byte], f5 func(byte) gigaparsec.Parser[byte, byte]) gigaparsec.Parser[byte, byte]
f := func(b byte) gigaparsec.Parser[byte, byte] {
return gigaparsec.Return[byte](b + 1)
}
p := func(bind5 Bind5T) gigaparsec.Parser[byte, byte] {
// gigaparsec.Bind5()
return bind5(gigaparsec.Match(byte(0)), f, f, f, f, f)
}
input := gigaparsec.SliceReaderAt[byte]{0}
b.Run("gigaparsec.Bind5", func(b *testing.B) {
for range b.N {
p(gigaparsec.Bind5).Run(input)
}
})
b.Run("naïve.Bind5", func(b *testing.B) {
for range b.N {
p(naive.Bind5).Run(input)
}
})
}
func BenchmarkBind9(b *testing.B) {
type Bind9T = func(p gigaparsec.Parser[byte, byte], f func(byte) gigaparsec.Parser[byte, byte], f2 func(byte) gigaparsec.Parser[byte, byte], f3 func(byte) gigaparsec.Parser[byte, byte], f4 func(byte) gigaparsec.Parser[byte, byte], f5 func(byte) gigaparsec.Parser[byte, byte], f6 func(byte) gigaparsec.Parser[byte, byte], f7 func(byte) gigaparsec.Parser[byte, byte], f8 func(byte) gigaparsec.Parser[byte, byte], f9 func(byte) gigaparsec.Parser[byte, byte]) gigaparsec.Parser[byte, byte]
f := func(b byte) gigaparsec.Parser[byte, byte] {
return gigaparsec.Return[byte](b + 1)
}
p := func(bind5 Bind9T) gigaparsec.Parser[byte, byte] {
// gigaparsec.Bind5()
return bind5(gigaparsec.Match(byte(0)), f, f, f, f, f, f, f, f, f)
}
input := gigaparsec.SliceReaderAt[byte]{0}
b.Run("gigaparsec.Bind9", func(b *testing.B) {
for range b.N {
p(gigaparsec.Bind9).Run(input)
}
})
b.Run("naive.Bind9", func(b *testing.B) {
for range b.N {
p(naive.Bind9).Run(input)
}
})
}
func BenchmarkSeq5(b *testing.B) {
type P = gigaparsec.Parser[byte, byte]
type Seq5T = func(P, P, P, P, P, func(byte, byte, byte, byte, byte) byte) P
zero := gigaparsec.Return[byte, byte](0)
f := func(a, b, c, d, e byte) byte {
return a + b + c + d + e
}
p := func(seq5 Seq5T) P {
return seq5(zero, zero, zero, zero, zero, f)
}
input := gigaparsec.SliceReaderAt[byte]{0}
b.Run("gigaparsec.Seq5", func(b *testing.B) {
for range b.N {
p(gigaparsec.Seq5).Run(input)
}
})
b.Run("naive.Seq5", func(b *testing.B) {
for range b.N {
p(naive.Seq5).Run(input)
}
})
}
func BenchmarkSeq9(b *testing.B) {
type P = gigaparsec.Parser[byte, byte]
type Seq9T = func(P, P, P, P, P, P, P, P, P, func(byte, byte, byte, byte, byte, byte, byte, byte, byte) byte) P
zero := gigaparsec.Return[byte, byte](0)
f := func(a, b, c, d, e, f, g, h, i byte) byte {
return a + b + c + d + e + f + g + h + i
}
p := func(seq9 Seq9T) P {
return seq9(zero, zero, zero, zero, zero, zero, zero, zero, zero, f)
}
input := gigaparsec.SliceReaderAt[byte]{0}
b.Run("gigaparsec.Seq9", func(b *testing.B) {
for range b.N {
p(gigaparsec.Seq9).Run(input)
}
})
b.Run("naive.Seq9", func(b *testing.B) {
for range b.N {
p(naive.Seq9).Run(input)
}
})
}
+79 -23
View File
@@ -4,6 +4,8 @@ package gigaparsec_test
import (
"bytes"
"errors"
"fmt"
"testing"
"git.codemonkeysoftware.net/b/gigaparsec"
@@ -14,7 +16,7 @@ import (
"pgregory.net/rapid"
)
func Todo(t *testing.T) {
func Todo[T interface{ Errorf(string, ...any) }](t T) {
t.Errorf("TODO")
}
@@ -29,8 +31,7 @@ func hasPrefix(prefix []byte) func([]byte) bool {
func TestSlice(t *testing.T) {
assertParseFails := func(t rapid.TB, input []byte, p gigaparsec.Parser[byte, []byte]) {
t.Helper()
start := gigaparsec.MakeState(bytes.NewReader(input))
result, err := p(start)
result, err := p.RunToResult(bytes.NewReader(input))
must.NoError(t, err)
success, _, _ := result.Status()
test.False(t, success)
@@ -56,7 +57,7 @@ func TestSlice(t *testing.T) {
expectedErr := generator.Error().Draw(t, "expectedErr")
r := ptest.ErrReaderAt(expectedErr)
s := rapid.SliceOfN(rapid.Byte(), 0, 100).Draw(t, "s")
result, err := gigaparsec.MatchSlice(s)(gigaparsec.MakeState(r))
result, err := gigaparsec.MatchSlice(s).RunToResult(r)
test.ErrorIs(t, err, expectedErr)
success, _, _ := result.Status()
test.False(t, success)
@@ -65,9 +66,8 @@ func TestSlice(t *testing.T) {
input := rapid.SliceOfN(rapid.Byte(), 1, -1).Draw(t, "input")
s := rapid.Map(rapid.IntRange(0, len(input)),
func(n int) []byte { return input[:n] }).Draw(t, "s")
start := gigaparsec.MakeState(bytes.NewReader(input))
result, err := gigaparsec.MatchSlice(s)(start)
result, err := gigaparsec.MatchSlice(s).RunToResult(bytes.NewReader(input))
must.NoError(t, err)
success, value, next := result.Status()
test.True(t, success)
@@ -88,7 +88,7 @@ func TestBind(t *testing.T) {
t.Run("returns an error if the second parser returns an error", Todo)
t.Run("succeeds if both parsers succeed", rapid.MakeCheck(func(t *rapid.T) {
// s := rapid.SliceOfN(rapid.Byte(), 0, 100)
t.Errorf("TODO")
Todo(t)
}))
/*
If the first parser fails, then consumption depends on the first parser.
@@ -107,7 +107,7 @@ func TestBind(t *testing.T) {
p := makeParser(pConsume)
q := func(struct{}) gigaparsec.Parser[byte, struct{}] { return makeParser(qConsume) }
result, err := gigaparsec.Bind(p, q)(gigaparsec.MakeState(bytes.NewReader(nil)))
result, err := gigaparsec.Bind(p, q).RunToResult(bytes.NewReader(nil))
must.NoError(t, err)
must.EqOp(t, pConsume || qConsume, result.Consumed())
}))
@@ -125,8 +125,31 @@ func TestSatisfy(t *testing.T) {
Todo(t)
}
func Try(t *testing.T) {
Todo(t)
func TestTry(t *testing.T) {
type R = ptest.ForcedResult
var cases = []struct{ P, TryP R }{
{P: R{Succeed: false, Consume: false}, TryP: R{Succeed: false, Consume: false}},
{P: R{Succeed: false, Consume: true}, TryP: R{Succeed: false, Consume: false}},
{P: R{Succeed: true, Consume: false}, TryP: R{Succeed: true, Consume: false}},
{P: R{Succeed: true, Consume: true}, TryP: R{Succeed: true, Consume: true}},
}
for _, c := range cases {
t.Run(fmt.Sprintf("%+v", c.P), func(t *testing.T) {
result, err := gigaparsec.Try(ptest.ForceResult).RunToResult(gigaparsec.SliceReaderAt[R]{c.P})
succeeded, _, _ := result.Status()
must.NoError(t, err)
test.EqOp(t, c.TryP.Succeed, succeeded)
test.EqOp(t, c.TryP.Consume, result.Consumed())
})
}
t.Run("fails on error", func(t *testing.T) {
expectedErr := errors.New("it broke")
p := gigaparsec.Try(gigaparsec.Match(byte(0)))
result, err := p.RunToResult(ptest.ErrReaderAt(expectedErr))
succeeded, _, _ := result.Status()
test.ErrorIs(t, err, expectedErr)
test.False(t, succeeded)
})
}
func TestLabel(t *testing.T) {
@@ -138,24 +161,57 @@ func TestEnd(t *testing.T) {
}
func TestRepeat(t *testing.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
t.Run("succeeds iff number of successes ≥ minCount", rapid.MakeCheck(func(t *rapid.T) {
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.RunToResult(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", 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)
t.Run("consumes iff at least one application consumes", rapid.MakeCheck(func(t *rapid.T) {
input := rapid.Map(rapid.SliceOfN(rapid.Just(ptest.ForcedResult{Succeed: true}), 0, 100),
func(ts []ptest.ForcedResult) []ptest.ForcedResult { return append(ts, ptest.ForcedResult{}) }).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, ptest.ForceResult).RunToResult(gigaparsec.SliceReaderAt[ptest.ForcedResult](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.RunToResult(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.RunToResult(ptest.ErrReaderAt(expectedErr))
succeeded, _, _ := result.Status()
test.ErrorIs(t, err, expectedErr)
test.False(t, succeeded)
})
}
func TestBracket(t *testing.T) {
Todo(t)
}
func TestWhere(t *testing.T) {
Todo(t)
}
+391 -5
View File
@@ -4,7 +4,8 @@
package gigaparsec
func Seq2[In, Out, T, T2 any](
// Seq runs 2 parsers in sequence and maps their collected results with f.
func Seq[In, Out, T, T2 any](
p Parser[In, T],
p2 Parser[In, T2],
f func(T, T2) Out,
@@ -34,10 +35,11 @@ func Seq2[In, Out, T, T2 any](
}
final := f(val, val2)
return Succeed(anyConsumed, final, next, MessageOK(s.Pos())), nil
return Succeed(anyConsumed, final, next, s.MessageOK()), nil
}
}
// Seq3 runs 3 parsers in sequence and maps their collected results with f.
func Seq3[In, Out, T, T2, T3 any](
p Parser[In, T],
p2 Parser[In, T2],
@@ -79,10 +81,11 @@ func Seq3[In, Out, T, T2, T3 any](
}
final := f(val, val2, val3)
return Succeed(anyConsumed, final, next, MessageOK(s.Pos())), nil
return Succeed(anyConsumed, final, next, s.MessageOK()), nil
}
}
// Seq4 runs 4 parsers in sequence and maps their collected results with f.
func Seq4[In, Out, T, T2, T3, T4 any](
p Parser[In, T],
p2 Parser[In, T2],
@@ -135,10 +138,11 @@ func Seq4[In, Out, T, T2, T3, T4 any](
}
final := f(val, val2, val3, val4)
return Succeed(anyConsumed, final, next, MessageOK(s.Pos())), nil
return Succeed(anyConsumed, final, next, s.MessageOK()), nil
}
}
// Seq5 runs 5 parsers in sequence and maps their collected results with f.
func Seq5[In, Out, T, T2, T3, T4, T5 any](
p Parser[In, T],
p2 Parser[In, T2],
@@ -202,6 +206,388 @@ func Seq5[In, Out, T, T2, T3, T4, T5 any](
}
final := f(val, val2, val3, val4, val5)
return Succeed(anyConsumed, final, next, MessageOK(s.Pos())), nil
return Succeed(anyConsumed, final, next, s.MessageOK()), nil
}
}
// Seq6 runs 6 parsers in sequence and maps their collected results with f.
func Seq6[In, Out, T, T2, T3, T4, T5, T6 any](
p Parser[In, T],
p2 Parser[In, T2],
p3 Parser[In, T3],
p4 Parser[In, T4],
p5 Parser[In, T5],
p6 Parser[In, T6],
f func(T, T2, T3, T4, T5, T6) Out,
) Parser[In, Out] {
return func(s State[In]) (Result[In, Out], error) {
var anyConsumed bool
var next = s
r, err := p(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r.Consumed()
success, val, next := r.Status()
if !success {
return Fail[In, Out](anyConsumed, r.Message()), nil
}
r2, err := p2(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r2.Consumed()
success, val2, next := r2.Status()
if !success {
return Fail[In, Out](anyConsumed, r2.Message()), nil
}
r3, err := p3(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r3.Consumed()
success, val3, next := r3.Status()
if !success {
return Fail[In, Out](anyConsumed, r3.Message()), nil
}
r4, err := p4(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r4.Consumed()
success, val4, next := r4.Status()
if !success {
return Fail[In, Out](anyConsumed, r4.Message()), nil
}
r5, err := p5(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r5.Consumed()
success, val5, next := r5.Status()
if !success {
return Fail[In, Out](anyConsumed, r5.Message()), nil
}
r6, err := p6(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r6.Consumed()
success, val6, next := r6.Status()
if !success {
return Fail[In, Out](anyConsumed, r6.Message()), nil
}
final := f(val, val2, val3, val4, val5, val6)
return Succeed(anyConsumed, final, next, s.MessageOK()), nil
}
}
// Seq7 runs 7 parsers in sequence and maps their collected results with f.
func Seq7[In, Out, T, T2, T3, T4, T5, T6, T7 any](
p Parser[In, T],
p2 Parser[In, T2],
p3 Parser[In, T3],
p4 Parser[In, T4],
p5 Parser[In, T5],
p6 Parser[In, T6],
p7 Parser[In, T7],
f func(T, T2, T3, T4, T5, T6, T7) Out,
) Parser[In, Out] {
return func(s State[In]) (Result[In, Out], error) {
var anyConsumed bool
var next = s
r, err := p(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r.Consumed()
success, val, next := r.Status()
if !success {
return Fail[In, Out](anyConsumed, r.Message()), nil
}
r2, err := p2(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r2.Consumed()
success, val2, next := r2.Status()
if !success {
return Fail[In, Out](anyConsumed, r2.Message()), nil
}
r3, err := p3(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r3.Consumed()
success, val3, next := r3.Status()
if !success {
return Fail[In, Out](anyConsumed, r3.Message()), nil
}
r4, err := p4(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r4.Consumed()
success, val4, next := r4.Status()
if !success {
return Fail[In, Out](anyConsumed, r4.Message()), nil
}
r5, err := p5(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r5.Consumed()
success, val5, next := r5.Status()
if !success {
return Fail[In, Out](anyConsumed, r5.Message()), nil
}
r6, err := p6(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r6.Consumed()
success, val6, next := r6.Status()
if !success {
return Fail[In, Out](anyConsumed, r6.Message()), nil
}
r7, err := p7(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r7.Consumed()
success, val7, next := r7.Status()
if !success {
return Fail[In, Out](anyConsumed, r7.Message()), nil
}
final := f(val, val2, val3, val4, val5, val6, val7)
return Succeed(anyConsumed, final, next, s.MessageOK()), nil
}
}
// Seq8 runs 8 parsers in sequence and maps their collected results with f.
func Seq8[In, Out, T, T2, T3, T4, T5, T6, T7, T8 any](
p Parser[In, T],
p2 Parser[In, T2],
p3 Parser[In, T3],
p4 Parser[In, T4],
p5 Parser[In, T5],
p6 Parser[In, T6],
p7 Parser[In, T7],
p8 Parser[In, T8],
f func(T, T2, T3, T4, T5, T6, T7, T8) Out,
) Parser[In, Out] {
return func(s State[In]) (Result[In, Out], error) {
var anyConsumed bool
var next = s
r, err := p(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r.Consumed()
success, val, next := r.Status()
if !success {
return Fail[In, Out](anyConsumed, r.Message()), nil
}
r2, err := p2(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r2.Consumed()
success, val2, next := r2.Status()
if !success {
return Fail[In, Out](anyConsumed, r2.Message()), nil
}
r3, err := p3(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r3.Consumed()
success, val3, next := r3.Status()
if !success {
return Fail[In, Out](anyConsumed, r3.Message()), nil
}
r4, err := p4(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r4.Consumed()
success, val4, next := r4.Status()
if !success {
return Fail[In, Out](anyConsumed, r4.Message()), nil
}
r5, err := p5(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r5.Consumed()
success, val5, next := r5.Status()
if !success {
return Fail[In, Out](anyConsumed, r5.Message()), nil
}
r6, err := p6(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r6.Consumed()
success, val6, next := r6.Status()
if !success {
return Fail[In, Out](anyConsumed, r6.Message()), nil
}
r7, err := p7(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r7.Consumed()
success, val7, next := r7.Status()
if !success {
return Fail[In, Out](anyConsumed, r7.Message()), nil
}
r8, err := p8(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r8.Consumed()
success, val8, next := r8.Status()
if !success {
return Fail[In, Out](anyConsumed, r8.Message()), nil
}
final := f(val, val2, val3, val4, val5, val6, val7, val8)
return Succeed(anyConsumed, final, next, s.MessageOK()), nil
}
}
// Seq9 runs 9 parsers in sequence and maps their collected results with f.
func Seq9[In, Out, T, T2, T3, T4, T5, T6, T7, T8, T9 any](
p Parser[In, T],
p2 Parser[In, T2],
p3 Parser[In, T3],
p4 Parser[In, T4],
p5 Parser[In, T5],
p6 Parser[In, T6],
p7 Parser[In, T7],
p8 Parser[In, T8],
p9 Parser[In, T9],
f func(T, T2, T3, T4, T5, T6, T7, T8, T9) Out,
) Parser[In, Out] {
return func(s State[In]) (Result[In, Out], error) {
var anyConsumed bool
var next = s
r, err := p(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r.Consumed()
success, val, next := r.Status()
if !success {
return Fail[In, Out](anyConsumed, r.Message()), nil
}
r2, err := p2(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r2.Consumed()
success, val2, next := r2.Status()
if !success {
return Fail[In, Out](anyConsumed, r2.Message()), nil
}
r3, err := p3(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r3.Consumed()
success, val3, next := r3.Status()
if !success {
return Fail[In, Out](anyConsumed, r3.Message()), nil
}
r4, err := p4(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r4.Consumed()
success, val4, next := r4.Status()
if !success {
return Fail[In, Out](anyConsumed, r4.Message()), nil
}
r5, err := p5(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r5.Consumed()
success, val5, next := r5.Status()
if !success {
return Fail[In, Out](anyConsumed, r5.Message()), nil
}
r6, err := p6(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r6.Consumed()
success, val6, next := r6.Status()
if !success {
return Fail[In, Out](anyConsumed, r6.Message()), nil
}
r7, err := p7(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r7.Consumed()
success, val7, next := r7.Status()
if !success {
return Fail[In, Out](anyConsumed, r7.Message()), nil
}
r8, err := p8(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r8.Consumed()
success, val8, next := r8.Status()
if !success {
return Fail[In, Out](anyConsumed, r8.Message()), nil
}
r9, err := p9(next)
if err != nil {
return Result[In, Out]{}, err
}
anyConsumed = anyConsumed || r9.Consumed()
success, val9, next := r9.Status()
if !success {
return Fail[In, Out](anyConsumed, r9.Message()), nil
}
final := f(val, val2, val3, val4, val5, val6, val7, val8, val9)
return Succeed(anyConsumed, final, next, s.MessageOK()), nil
}
}
+20
View File
@@ -4,6 +4,7 @@
package test
import (
"errors"
"io"
"git.codemonkeysoftware.net/b/gigaparsec"
@@ -34,3 +35,22 @@ func SliceOfN[T any](value T, n int) []T {
}
return s
}
type ForcedResult struct{ Succeed, Consume bool }
func ForceResult(state gigaparsec.State[ForcedResult]) (gigaparsec.Result[ForcedResult, struct{}], error) {
buf := make([]ForcedResult, 1)
_, next, err := state.Read(buf)
if errors.Is(err, io.EOF) {
return gigaparsec.Fail[ForcedResult, struct{}](false, state.MessageEnd()), nil
}
if err != nil {
return gigaparsec.Result[ForcedResult, struct{}]{}, err
}
tok := buf[0]
if tok.Succeed {
return gigaparsec.Succeed(tok.Consume, struct{}{}, next, state.MessageOK()), nil
} else {
return gigaparsec.Fail[ForcedResult, struct{}](tok.Consume, state.Message("Succeed=false", "Succeed=true")), nil
}
}