Compare 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
15 changed files with 205 additions and 120 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 ./...
+1 -4
View File
@@ -1,6 +1,3 @@
Think about not requiring so much Pos() when making messages
Think about changing "consume" to "commit"
Should MakeState be private now that there's Run?
What's Megaparsec got that we ain't got?
What’s Megaparsec got that we ain’t got?
chainl
whitespace handling
+10 -10
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 p’s 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,7 +275,7 @@ 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
}
}
@@ -363,7 +363,7 @@ func Bind6[In, Out, T, T2, T3, T4, T5, T6 any](
return Fail[In, Out](anyConsumed, r7.Message()), nil
}
return Succeed(anyConsumed, val7, next, MessageOK(s.Pos())), nil
return Succeed(anyConsumed, val7, next, s.MessageOK()), nil
}
}
@@ -462,7 +462,7 @@ func Bind7[In, Out, T, T2, T3, T4, T5, T6, T7 any](
return Fail[In, Out](anyConsumed, r8.Message()), nil
}
return Succeed(anyConsumed, val8, next, MessageOK(s.Pos())), nil
return Succeed(anyConsumed, val8, next, s.MessageOK()), nil
}
}
@@ -572,7 +572,7 @@ func Bind8[In, Out, T, T2, T3, T4, T5, T6, T7, T8 any](
return Fail[In, Out](anyConsumed, r9.Message()), nil
}
return Succeed(anyConsumed, val9, next, MessageOK(s.Pos())), nil
return Succeed(anyConsumed, val9, next, s.MessageOK()), nil
}
}
@@ -693,6 +693,6 @@ func Bind9[In, Out, T, T2, T3, T4, T5, T6, T7, T8, T9 any](
return Fail[In, Out](anyConsumed, r10.Message()), nil
}
return Succeed(anyConsumed, val10, next, MessageOK(s.Pos())), nil
return Succeed(anyConsumed, val10, next, s.MessageOK()), nil
}
}
+15 -2
View File
@@ -1,11 +1,15 @@
// SPDX-License-Identifier: Unlicense
package bytes
import (
"git.codemonkeysoftware.net/b/gigaparsec"
)
func Token[Out, WSOut any](whitespace gigaparsec.Parser[byte, WSOut]) func(p gigaparsec.Parser[byte, Out]) gigaparsec.Parser[byte, Out] {
mappedWS := gigaparsec.Map(whitespace, func(WSOut) struct{} { return struct{}{} })
// 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
@@ -14,3 +18,12 @@ func Token[Out, WSOut any](whitespace gigaparsec.Parser[byte, WSOut]) func(p gig
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
}
+6 -6
View File
@@ -70,14 +70,14 @@ func Regexp(pattern string) gigaparsec.Parser[byte, string] {
}
if idx == nil {
if err == io.EOF {
return gigaparsec.Fail[byte, string](false, gigaparsec.MessageEnd(input.Pos())), nil
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.
@@ -87,7 +87,7 @@ func Regexp(pattern string) gigaparsec.Parser[byte, string] {
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
}
}
@@ -98,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
}
}
+6 -6
View File
@@ -25,7 +25,7 @@ func TestRegexp(t *testing.T) {
Draw(t, "input")
p := pbytes.Regexp(string(needle))
result, err := p(gigaparsec.MakeState(bytes.NewReader(input)))
result, err := p.RunToResult(bytes.NewReader(input))
succeeded, val, next := result.Status()
must.NoError(t, err)
@@ -40,7 +40,7 @@ func TestRegexp(t *testing.T) {
Draw(t, "input")
p := pbytes.Regexp(string(needle))
result, err := p(gigaparsec.MakeState(bytes.NewReader(input)))
result, err := p.RunToResult(bytes.NewReader(input))
succeeded, _, _ := result.Status()
must.NoError(t, err)
@@ -49,21 +49,21 @@ func TestRegexp(t *testing.T) {
t.Run("fails on unexpected error", func(t *testing.T) {
expectedErr := errors.New("it broke")
p := pbytes.Regexp("nope")
result, err := p(gigaparsec.MakeState(ptest.ErrReaderAt(expectedErr)))
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(gigaparsec.MakeState(strings.NewReader("hellaparsec")))
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(gigaparsec.MakeState(strings.NewReader("")))
result, err := p.RunToResult(strings.NewReader(""))
succeeded, value, _ := result.Status()
must.NoError(t, err)
must.True(t, succeeded)
@@ -71,7 +71,7 @@ func TestRegexp(t *testing.T) {
})
t.Run("fails without an error at EOF", func(t *testing.T) {
p := pbytes.Regexp("a")
result, err := p(gigaparsec.MakeState(strings.NewReader("")))
result, err := p.RunToResult(strings.NewReader(""))
succeeded, _, _ := result.Status()
must.NoError(t, err)
must.False(t, succeeded)
+76 -55
View File
@@ -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.
// It’s mostly useful for testing. You probably don’t 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 State’s 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 State’s 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 State’s 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,6 +207,7 @@ func (p Parser[In, Out]) Label(label string) Parser[In, Out] {
}
}
// 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)
@@ -205,15 +219,22 @@ func (p Parser[In, Out]) Where(pred func(Out) bool) Parser[In, Out] {
}
}
type ParseError Message
func (pe ParseError) Error() string {
return Message(pe).String()
// 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))
})
}
func Run[In, Out any](p Parser[In, Out], r ReaderAt[In]) (out Out, err error) {
// 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
@@ -226,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
}
}
@@ -240,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
@@ -249,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
}
}
@@ -263,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
}
}
@@ -283,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
}
}
@@ -328,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
}
@@ -355,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.
@@ -405,7 +425,7 @@ func Repeat[In, Out any](minCount int, p Parser[In, Out]) Parser[In, []Out] {
success, value, nextState := result.Status()
if !success {
if len(values) >= minCount {
return Succeed(consumed, values, currState, MessageOK(state.Pos())), nil
return Succeed(consumed, values, currState, state.MessageOK()), nil
}
return Fail[In, []Out](consumed, result.Message()), nil
}
@@ -424,6 +444,7 @@ func Lazy[In, Out any](p func() Parser[In, Out]) Parser[In, Out] {
}
}
// 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
+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 p’s 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 -}}
+1 -1
View File
@@ -22,7 +22,7 @@ func Seq{{if gt . 2}}{{.}}{{end}}[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 -}}
+8 -8
View File
@@ -22,13 +22,13 @@ func BenchmarkBind5(b *testing.B) {
input := gigaparsec.SliceReaderAt[byte]{0}
b.Run("gigaparsec.Bind5", func(b *testing.B) {
for range b.N {
gigaparsec.Run(p(gigaparsec.Bind5), input)
p(gigaparsec.Bind5).Run(input)
}
})
b.Run("naïve.Bind5", func(b *testing.B) {
for range b.N {
gigaparsec.Run(p(naive.Bind5), input)
p(naive.Bind5).Run(input)
}
})
}
@@ -46,13 +46,13 @@ func BenchmarkBind9(b *testing.B) {
input := gigaparsec.SliceReaderAt[byte]{0}
b.Run("gigaparsec.Bind9", func(b *testing.B) {
for range b.N {
gigaparsec.Run(p(gigaparsec.Bind9), input)
p(gigaparsec.Bind9).Run(input)
}
})
b.Run("naive.Bind9", func(b *testing.B) {
for range b.N {
gigaparsec.Run(p(naive.Bind9), input)
p(naive.Bind9).Run(input)
}
})
}
@@ -72,12 +72,12 @@ func BenchmarkSeq5(b *testing.B) {
input := gigaparsec.SliceReaderAt[byte]{0}
b.Run("gigaparsec.Seq5", func(b *testing.B) {
for range b.N {
gigaparsec.Run(p(gigaparsec.Seq5), input)
p(gigaparsec.Seq5).Run(input)
}
})
b.Run("naive.Seq5", func(b *testing.B) {
for range b.N {
gigaparsec.Run(p(naive.Seq5), input)
p(naive.Seq5).Run(input)
}
})
}
@@ -97,12 +97,12 @@ func BenchmarkSeq9(b *testing.B) {
input := gigaparsec.SliceReaderAt[byte]{0}
b.Run("gigaparsec.Seq9", func(b *testing.B) {
for range b.N {
gigaparsec.Run(p(gigaparsec.Seq9), input)
p(gigaparsec.Seq9).Run(input)
}
})
b.Run("naive.Seq9", func(b *testing.B) {
for range b.N {
gigaparsec.Run(p(naive.Seq9), input)
p(naive.Seq9).Run(input)
}
})
}
+10 -13
View File
@@ -31,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)
@@ -58,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)
@@ -67,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)
@@ -109,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())
}))
@@ -137,8 +135,7 @@ func TestTry(t *testing.T) {
}
for _, c := range cases {
t.Run(fmt.Sprintf("%+v", c.P), func(t *testing.T) {
start := gigaparsec.MakeState(gigaparsec.SliceReaderAt[R]{c.P})
result, err := gigaparsec.Try(ptest.ForceResult)(start)
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)
@@ -148,7 +145,7 @@ func TestTry(t *testing.T) {
t.Run("fails on error", func(t *testing.T) {
expectedErr := errors.New("it broke")
p := gigaparsec.Try(gigaparsec.Match(byte(0)))
result, err := p(gigaparsec.MakeState(ptest.ErrReaderAt(expectedErr)))
result, err := p.RunToResult(ptest.ErrReaderAt(expectedErr))
succeeded, _, _ := result.Status()
test.ErrorIs(t, err, expectedErr)
test.False(t, succeeded)
@@ -172,7 +169,7 @@ func TestRepeat(t *testing.T) {
input := append(ptest.SliceOfN(true, successes), false)
p := gigaparsec.Repeat(minCount, gigaparsec.Match(true))
result, err := p(gigaparsec.MakeState(gigaparsec.SliceReaderAt[bool](input)))
result, err := p.RunToResult(gigaparsec.SliceReaderAt[bool](input))
must.NoError(t, err)
success, _, next := result.Status()
@@ -190,21 +187,21 @@ func TestRepeat(t *testing.T) {
}
shouldConsume := consumeAt != nil
result, err := gigaparsec.Repeat(0, ptest.ForceResult)(gigaparsec.MakeState(gigaparsec.SliceReaderAt[ptest.ForcedResult](input)))
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(gigaparsec.MakeState(gigaparsec.SliceReaderAt[int](nil)))
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(gigaparsec.MakeState(ptest.ErrReaderAt(expectedErr)))
result, err := p.RunToResult(ptest.ErrReaderAt(expectedErr))
succeeded, _, _ := result.Status()
test.ErrorIs(t, err, expectedErr)
test.False(t, succeeded)
+8 -8
View File
@@ -35,7 +35,7 @@ func Seq[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
}
}
@@ -81,7 +81,7 @@ 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
}
}
@@ -138,7 +138,7 @@ 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
}
}
@@ -206,7 +206,7 @@ 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
}
}
@@ -285,7 +285,7 @@ func Seq6[In, Out, T, T2, T3, T4, T5, T6 any](
}
final := f(val, val2, val3, val4, val5, val6)
return Succeed(anyConsumed, final, next, MessageOK(s.Pos())), nil
return Succeed(anyConsumed, final, next, s.MessageOK()), nil
}
}
@@ -375,7 +375,7 @@ func Seq7[In, Out, T, T2, T3, T4, T5, T6, T7 any](
}
final := f(val, val2, val3, val4, val5, val6, val7)
return Succeed(anyConsumed, final, next, MessageOK(s.Pos())), nil
return Succeed(anyConsumed, final, next, s.MessageOK()), nil
}
}
@@ -476,7 +476,7 @@ func Seq8[In, Out, T, T2, T3, T4, T5, T6, T7, T8 any](
}
final := f(val, val2, val3, val4, val5, val6, val7, val8)
return Succeed(anyConsumed, final, next, MessageOK(s.Pos())), nil
return Succeed(anyConsumed, final, next, s.MessageOK()), nil
}
}
@@ -588,6 +588,6 @@ func Seq9[In, Out, T, T2, T3, T4, T5, T6, T7, T8, T9 any](
}
final := f(val, val2, val3, val4, val5, val6, val7, val8, val9)
return Succeed(anyConsumed, final, next, MessageOK(s.Pos())), nil
return Succeed(anyConsumed, final, next, s.MessageOK()), nil
}
}
+3 -3
View File
@@ -42,15 +42,15 @@ func ForceResult(state gigaparsec.State[ForcedResult]) (gigaparsec.Result[Forced
buf := make([]ForcedResult, 1)
_, next, err := state.Read(buf)
if errors.Is(err, io.EOF) {
return gigaparsec.Fail[ForcedResult, struct{}](false, gigaparsec.MessageEnd(state.Pos())), nil
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, gigaparsec.MessageOK(state.Pos())), nil
return gigaparsec.Succeed(tok.Consume, struct{}{}, next, state.MessageOK()), nil
} else {
return gigaparsec.Fail[ForcedResult, struct{}](tok.Consume, gigaparsec.MakeMessage(state.Pos(), "Succeed=false", "Succeed=true")), nil
return gigaparsec.Fail[ForcedResult, struct{}](tok.Consume, state.Message("Succeed=false", "Succeed=true")), nil
}
}