Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Count #20

Merged
merged 2 commits into from
Jan 30, 2024
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ func main() {
}

// We want 3 hex pairs
pairs, _, err := parser.Many(hexPair, hexPair, hexPair)(colour)
pairs, _, err := parser.Count(hexPair, 3)(colour)
if err != nil {
log.Fatalln(err)
}
Expand Down
12 changes: 12 additions & 0 deletions bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,3 +199,15 @@ func BenchmarkMany(b *testing.B) {
}
}
}

func BenchmarkCount(b *testing.B) {
input := "abcabcabc"

b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _, err := parser.Count(parser.Exact("abc"), 3)(input)
if err != nil {
b.Fatal(err)
}
}
}
2 changes: 1 addition & 1 deletion example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func Example() {
}

// We want 3 hex pairs
pairs, _, err := parser.Many(hexPair, hexPair, hexPair)(colour)
pairs, _, err := parser.Count(hexPair, 3)(colour)
if err != nil {
fmt.Fprintln(os.Stderr, err)
return
Expand Down
28 changes: 27 additions & 1 deletion parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -593,7 +593,7 @@ func Try[T any](parsers ...Parser[T]) Parser[T] {
}
}

// Many returns a [Parser] that chains a series of sub-parsers, passing the remainder from one
// Many returns a [Parser] that calls a series of sub-parsers, passing the remainder from one
// as input to the next and returning a slice of values; one from each parser, and any remaining input
// after applying all the parsers.
//
Expand Down Expand Up @@ -621,3 +621,29 @@ func Many[T any](parsers ...Parser[T]) Parser[[]T] {
return values, finalRemainder, nil
}
}

// Count returns a [Parser] that applies another parser a certain number of times,
// returning the values in a slice along with any remaining input.
//
// If the parser fails or the input is exhausted before the parser has been applied
// the requested number of times, an error will be returned.
func Count[T any](parser Parser[T], count int) Parser[[]T] {
return func(input string) ([]T, string, error) {
values := make([]T, 0, count)

nextInput := input // The input to the next parser in the loop, starts as our overall input
var finalRemainder string // The final remainder to eventually return, updated with each parser

for i := 0; i < count; i++ {
value, remainder, err := parser(nextInput)
if err != nil {
return nil, "", fmt.Errorf("Count: parser failed: %w", err)
}
values = append(values, value)
nextInput = remainder
finalRemainder = remainder
}

return values, finalRemainder, nil
}
}
91 changes: 91 additions & 0 deletions parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1771,6 +1771,82 @@ func TestMany(t *testing.T) {
}
}

func TestCount(t *testing.T) {
type test[T any] struct {
p parser.Parser[T] // The parser to apply
name string // Identifying test case name
input string // Input to the parser
remainder string // Expected remainder after parsing
err string // The expected error message, if there was one
value []T // The expected value after parsing
count int // Number of times to apply p to input
wantErr bool // Whether or not we wanted an error
}

tests := []test[string]{
{
name: "empty input",
input: "",
p: parser.Take(2),
count: 2,
value: nil,
remainder: "",
wantErr: true,
err: "Count: parser failed: Take: cannot take from empty input",
},
{
name: "take pairs",
input: "123456",
p: parser.Take(2),
count: 3,
value: []string{"12", "34", "56"},
remainder: "",
wantErr: false,
err: "",
},
{
name: "input too short",
input: "abcabcabc",
p: parser.Exact("abc"),
count: 4,
value: nil,
remainder: "",
wantErr: true,
err: "Count: parser failed: Exact: cannot match on empty input",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
value, remainder, err := parser.Count(tt.p, tt.count)(tt.input)

// Can't use the helper as []string is not comparable

// Should only error if we wanted one
if (err != nil) != tt.wantErr {
t.Fatalf("\nGot error:\t%v\nWanted error:\t%v\n", err, tt.wantErr)
}

// If we did get an error, the message should match what we expect
if err != nil {
if msg := err.Error(); msg != tt.err {
t.Fatalf("\nError message:\t%q\nWanted:\t%q\n", msg, tt.err)
}
}

// The value should be as expected
if !reflect.DeepEqual(value, tt.value) {
t.Errorf("\nValue:\t%#v\nWanted:\t%#v\n", value, tt.value)
}

// Likewise the remainder
if remainder != tt.remainder {
t.Errorf("\nRemainder:\t%q\nWanted:\t%q\n", remainder, tt.remainder)
}
})
}
}

func ExampleTake() {
input := "Hello I am some input for you to parser"

Expand Down Expand Up @@ -2023,6 +2099,21 @@ func ExampleMany() {
// Remainder: "rest..."
}

func ExampleCount() {
input := "12345678rest..." // Pairs of digits with a bit on the end

value, remainder, err := parser.Count(parser.Take(2), 4)(input)
if err != nil {
fmt.Fprintln(os.Stderr, err)
}

fmt.Printf("Value: %#v\n", value)
fmt.Printf("Remainder: %q\n", remainder)

// Output: Value: []string{"12", "34", "56", "78"}
// Remainder: "rest..."
}

// parserTest is a simple structure to encapsulate everything we need to test about
// the result of applying a parser to some input.
type parserTest[T comparable] struct {
Expand Down
Loading