From 10ace0a1a7f50904f65a982786c64a65bb8b2ab7 Mon Sep 17 00:00:00 2001 From: Tom Fleet Date: Sun, 28 Jan 2024 17:11:12 +0000 Subject: [PATCH] Fix predicate error cases --- .gitignore | 4 ++++ parser.go | 21 +++++++++++++++------ parser_test.go | 16 ++++++++-------- 3 files changed, 27 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index b92d69a..82527cd 100755 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,10 @@ # Test binary, built with `go test -c` *.test +# Comparative benchmarks +before.txt +after.txt + # Output of the go coverage tool, specifically when used with LiteIDE *.out coverage.html diff --git a/parser.go b/parser.go index 84acbd9..b790daa 100644 --- a/parser.go +++ b/parser.go @@ -158,8 +158,7 @@ func Char(char rune) Parser[string] { // // If the predicate doesn't return false before the entire input is consumed, an error will be returned. // -// A predicate that never returns true will leave the entire input unparsed and return a -// value that is an empty string, and a remainder that is the entire input. +// A predicate that never returns true will also return an error. func TakeWhile(predicate func(r rune) bool) Parser[string] { return func(input string) (string, string, error) { if input == "" { @@ -174,6 +173,10 @@ func TakeWhile(predicate func(r rune) bool) Parser[string] { return "", "", errors.New("TakeWhile: predicate must be a non-nil function") } + if strings.IndexFunc(input, predicate) == -1 { + return "", "", errors.New("TakeWhile: predicate never returned true") + } + end := 0 // Byte position of last rune that the predicate returns true for broken := false // Whether the predicate ever returned false so we broke the loop for pos, char := range input { @@ -200,11 +203,9 @@ func TakeWhile(predicate func(r rune) bool) Parser[string] { // // If the input is empty or predicate == nil, an error will be returned. // -// If the predicate doesn't return true before the entire input is consumed, an error will be returned -// . +// If the predicate doesn't return true before the entire input is consumed, an error will be returned. // -// A predicate that never returns false will leave the entire input unparsed and return a -// value that is an empty string, and a remainder that is the entire input. +// A predicate that never returns false will also return an error. func TakeUntil(predicate func(r rune) bool) Parser[string] { return func(input string) (string, string, error) { if input == "" { @@ -219,6 +220,14 @@ func TakeUntil(predicate func(r rune) bool) Parser[string] { return "", "", errors.New("TakeUntil: predicate must be a non-nil function") } + // strings.IndexFunc returns the index of the first char for which the predicate + // returns true, we want the opposite to test if it ever returns false + notPred := func(r rune) bool { return !predicate(r) } + + if strings.IndexFunc(input, notPred) == -1 { + return "", "", errors.New("TakeUntil: predicate never returned false") + } + end := 0 // Byte position of last rune that the predicate returns false for broken := false // Whether the predicate ever returned true so we broke the loop for pos, char := range input { diff --git a/parser_test.go b/parser_test.go index 344da69..0af33f0 100644 --- a/parser_test.go +++ b/parser_test.go @@ -584,7 +584,7 @@ func TestTakeWhile(t *testing.T) { err: "TakeWhile: predicate must be a non-nil function", }, { - name: "predicate never returns false", // Good libraries don't allow infinite loops + name: "predicate never returns false", input: "fixed length input", value: "", remainder: "", @@ -596,10 +596,10 @@ func TestTakeWhile(t *testing.T) { name: "predicate never returns true", input: "fixed length input", value: "", - remainder: "fixed length input", + remainder: "", predicate: func(r rune) bool { return false }, - wantErr: false, - err: "", + wantErr: true, + err: "TakeWhile: predicate never returned true", }, { name: "consume whitespace", @@ -839,7 +839,7 @@ func TestTakeUntil(t *testing.T) { err: "TakeUntil: predicate must be a non-nil function", }, { - name: "predicate never returns true", // Good libraries don't allow infinite loops + name: "predicate never returns true", input: "fixed length input", value: "", remainder: "", @@ -851,10 +851,10 @@ func TestTakeUntil(t *testing.T) { name: "predicate never returns false", input: "fixed length input", value: "", - remainder: "fixed length input", + remainder: "", predicate: func(r rune) bool { return true }, - wantErr: false, - err: "", + wantErr: true, + err: "TakeUntil: predicate never returned false", }, { name: "consume until whitespace",