Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions core/all_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ func TestAll(t *testing.T) {
ExpectedOK: false,
RemainingInput: "AC",
},
{
Name: "no match rolls back input even if one of the parsers consumed input",
Input: "AC",
Parser: core.All(NaughtyParser[string](), core.Rune('C')),
ExpectedOK: false,
RemainingInput: "AC",
},
{
Name: "match",
Input: "AB",
Expand Down
2 changes: 2 additions & 0 deletions core/any.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@ package core
// Any looks for matches in the given parsers, returning the first match or rolls back the input if no match is found.
func Any[T any](parsers ...Parser[T]) Parser[T] {
return func(in Input) (T, bool, error) {
start := in.Checkpoint()
for _, parser := range parsers {
match, ok, err := parser(in)
if err != nil || ok {
return match, true, err
}
}
var t T
in.Restore(start)
return t, false, nil
}
}
7 changes: 7 additions & 0 deletions core/any_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ func TestAny(t *testing.T) {
ExpectedOK: false,
RemainingInput: "C",
},
{
Name: "no match rolls back input even if one of the parsers consumed input",
Input: "C",
Parser: core.Any(NaughtyParser[string](), core.Rune('C')),
ExpectedOK: false,
RemainingInput: "C",
},
{
Name: "match",
Input: "B",
Expand Down
2 changes: 2 additions & 0 deletions core/or.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package core
// It returns as soon as one of the parsers are successful or rolls back when none are.
func Or[A any, B any](a Parser[A], b Parser[B]) Parser[Tuple2[Match[A], Match[B]]] {
return func(in Input) (Tuple2[Match[A], Match[B]], bool, error) {
start := in.Checkpoint()
var res tuple2[Match[A], Match[B]]

matchA, okA, errA := a(in)
Expand All @@ -38,6 +39,7 @@ func Or[A any, B any](a Parser[A], b Parser[B]) Parser[Tuple2[Match[A], Match[B]
return res, true, nil
}

in.Restore(start)
return res, false, nil
}
}
8 changes: 8 additions & 0 deletions core/or_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@ func TestOr(t *testing.T) {
ExpectedOK: false,
RemainingInput: "C",
},
{
Name: "no match rolls back input even if one of the parsers consumed input",
Input: "C",
Parser: Or(NaughtyParser[string](), Rune('C')),
ExpectedMatch: NewTuple2(NewMatch("", false), NewMatch("", false)),
ExpectedOK: false,
RemainingInput: "C",
},
{
Name: "first match",
Input: "A",
Expand Down
64 changes: 64 additions & 0 deletions core/sequences_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@ func TestSequence2(t *testing.T) {
ExpectedOK: false,
RemainingInput: "AB",
},
{
Name: "no match rolls back input even if one of the parsers consumed input",
Input: "AB",
Parser: core.SequenceOf2(NaughtyParser[string](), core.String("B")),
ExpectedMatch: core.NewTuple2("", ""),
ExpectedOK: false,
RemainingInput: "AB",
},
{
Name: "partial match",
Input: "AB",
Expand Down Expand Up @@ -60,6 +68,14 @@ func TestSequence3(t *testing.T) {
ExpectedOK: false,
RemainingInput: "ABC",
},
{
Name: "no match rolls back input even if one of the parsers consumed input",
Input: "ABC",
Parser: core.SequenceOf3(NaughtyParser[string](), core.String("B"), core.String("C")),
ExpectedMatch: core.NewTuple3("", "", ""),
ExpectedOK: false,
RemainingInput: "ABC",
},
{
Name: "partial match",
Input: "ABC",
Expand Down Expand Up @@ -89,6 +105,14 @@ func TestSequence4(t *testing.T) {
ExpectedOK: false,
RemainingInput: "ABCD",
},
{
Name: "no match rolls back input even if one of the parsers consumed input",
Input: "ABCD",
Parser: core.SequenceOf4(NaughtyParser[string](), core.String("B"), core.String("C"), core.String("D")),
ExpectedMatch: core.NewTuple4("", "", "", ""),
ExpectedOK: false,
RemainingInput: "ABCD",
},
{
Name: "partial match",
Input: "ABCD",
Expand Down Expand Up @@ -118,6 +142,14 @@ func TestSequence5(t *testing.T) {
ExpectedOK: false,
RemainingInput: "ABCDE",
},
{
Name: "no match rolls back input even if one of the parsers consumed input",
Input: "ABCDE",
Parser: core.SequenceOf5(NaughtyParser[string](), core.String("B"), core.String("C"), core.String("D"), core.String("E")),
ExpectedMatch: core.NewTuple5("", "", "", "", ""),
ExpectedOK: false,
RemainingInput: "ABCDE",
},
{
Name: "partial match",
Input: "ABCDE",
Expand Down Expand Up @@ -147,6 +179,14 @@ func TestSequence6(t *testing.T) {
ExpectedOK: false,
RemainingInput: "ABCDEF",
},
{
Name: "no match rolls back input even if one of the parsers consumed input",
Input: "ABCDEF",
Parser: core.SequenceOf6(NaughtyParser[string](), core.String("B"), core.String("C"), core.String("D"), core.String("E"), core.String("F")),
ExpectedMatch: core.NewTuple6("", "", "", "", "", ""),
ExpectedOK: false,
RemainingInput: "ABCDEF",
},
{
Name: "partial match",
Input: "ABCDEF",
Expand Down Expand Up @@ -176,6 +216,14 @@ func TestSequence7(t *testing.T) {
ExpectedOK: false,
RemainingInput: "ABCDEFG",
},
{
Name: "no match rolls back input even if one of the parsers consumed input",
Input: "ABCDEFG",
Parser: core.SequenceOf7(NaughtyParser[string](), core.String("B"), core.String("C"), core.String("D"), core.String("E"), core.String("F"), core.String("G")),
ExpectedMatch: core.NewTuple7("", "", "", "", "", "", ""),
ExpectedOK: false,
RemainingInput: "ABCDEFG",
},
{
Name: "partial match",
Input: "ABCDEFG",
Expand Down Expand Up @@ -205,6 +253,14 @@ func TestSequence8(t *testing.T) {
ExpectedOK: false,
RemainingInput: "ABCDEFGH",
},
{
Name: "no match rolls back input even if one of the parsers consumed input",
Input: "ABCDEFGH",
Parser: core.SequenceOf8(NaughtyParser[string](), core.String("B"), core.String("C"), core.String("D"), core.String("E"), core.String("F"), core.String("G"), core.String("H")),
ExpectedMatch: core.NewTuple8("", "", "", "", "", "", "", ""),
ExpectedOK: false,
RemainingInput: "ABCDEFGH",
},
{
Name: "partial match",
Input: "ABCDEFGH",
Expand Down Expand Up @@ -234,6 +290,14 @@ func TestSequence9(t *testing.T) {
ExpectedOK: false,
RemainingInput: "ABCDEFGHI",
},
{
Name: "no match rolls back input even if one of the parsers consumed input",
Input: "ABCDEFGHI",
Parser: core.SequenceOf9(NaughtyParser[string](), core.String("B"), core.String("C"), core.String("D"), core.String("E"), core.String("F"), core.String("G"), core.String("H"), core.String("I")),
ExpectedMatch: core.NewTuple9("", "", "", "", "", "", "", "", ""),
ExpectedOK: false,
RemainingInput: "ABCDEFGHI",
},
{
Name: "partial match",
Input: "ABCDEFGHI",
Expand Down
7 changes: 7 additions & 0 deletions core/times_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ func TestTime(t *testing.T) {
ExpectedMatch: []string{"A", "A"},
ExpectedOK: true,
},
{
Name: "Times: no match rolls back input even if one of the parsers consumed input",
Input: "AC",
Parser: core.Times(2, NaughtyParser[string]()),
ExpectedOK: false,
RemainingInput: "AC",
},
{
Name: "Between: at least 1 up to 5 with 4",
Input: "AAAA",
Expand Down
18 changes: 14 additions & 4 deletions test/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ package core_test
import (
"testing"

"github.com/liamawhite/parse/core"
. "github.com/liamawhite/parse/core"
"github.com/stretchr/testify/assert"
)

type ParserTest[T any] struct {
Name string
Input string
Parser core.Parser[T]
Parser Parser[T]
ExpectedMatch T
ExpectedOK bool
WantErr bool
Expand All @@ -34,7 +34,7 @@ type ParserTest[T any] struct {
func RunTests[T any](t *testing.T, tests []ParserTest[T]) {
for _, test := range tests {
t.Run(test.Name, func(t *testing.T) {
in := core.NewInput(test.Input)
in := NewInput(test.Input)
match, ok, err := test.Parser(in)
assert.Equal(t, test.ExpectedOK, ok)
assert.Equal(t, test.ExpectedMatch, match)
Expand All @@ -44,7 +44,7 @@ func RunTests[T any](t *testing.T, tests []ParserTest[T]) {
assert.NoError(t, err)
}

remaining, _, _ := core.StringWhileNot(core.EOF[string]())(in)
remaining, _, _ := StringWhileNot(EOF[string]())(in)
assert.Equal(t, test.RemainingInput, remaining)
})
}
Expand All @@ -60,3 +60,13 @@ func CaPiTaLiZe(s string) string {
}
return string(runes)
}

// NaughtyParser parser consumes input but does not rollback
// This is useful for testing that combinators rollback input correctly
func NaughtyParser[T any]() Parser[T] {
return func(in Input) (T, bool, error) {
in.Take(1)
var res T
return res, false, nil
}
}
Loading