From e31fb00638766189690cc0134ab4a3e05400a155 Mon Sep 17 00:00:00 2001 From: Gordon Allott Date: Thu, 29 Feb 2024 12:32:32 +0000 Subject: [PATCH] avoid returning errors when one-or-more groups have no matches (#390) --- error_test.go | 27 +++++++++++++++++++++++++++ nodes.go | 3 ++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/error_test.go b/error_test.go index c076363e..e44ccea3 100644 --- a/error_test.go +++ b/error_test.go @@ -45,6 +45,33 @@ func TestErrorReporting(t *testing.T) { require.EqualError(t, err, `1:20: unexpected token ")" (expected )`) } +func TestMoreThanOneErrors(t *testing.T) { + type unionMatchAtLeastOnce struct { + Ident string `( @Ident ` + String string `| @String+ ` + Float float64 `| @Float )` + } + type union struct { + Ident string `( @Ident ` + String string `| @String ` + Float float64 `| @Float )` + } + + pAtLeastOnce := mustTestParser[unionMatchAtLeastOnce](t, participle.Unquote("String")) + p := mustTestParser[union](t, participle.Unquote("String")) + + ast, err := pAtLeastOnce.ParseString("", `"a string" "two strings"`) + require.NoError(t, err) + require.Equal(t, &unionMatchAtLeastOnce{String: "a stringtwo strings"}, ast) + + _, err = p.ParseString("", `102`) + require.EqualError(t, err, `1:1: unexpected token "102"`) + + _, err = pAtLeastOnce.ParseString("", `102`) + // ensure we don't get a "+1:1: sub-expression + must match at least once" error + require.EqualError(t, err, `1:1: unexpected token "102"`) +} + func TestErrorWrap(t *testing.T) { expected := errors.New("badbad") err := participle.Wrapf(lexer.Position{Line: 1, Column: 1}, expected, "bad: %s", "thing") diff --git a/nodes.go b/nodes.go index 808a59d9..737b12c8 100644 --- a/nodes.go +++ b/nodes.go @@ -282,7 +282,8 @@ func (g *group) Parse(ctx *parseContext, parent reflect.Value) (out []reflect.Va if matches >= MaxIterations { return nil, Errorf(t.Pos, "too many iterations of %s (> %d)", g, MaxIterations) } - if matches < min { + // avoid returning errors in parent nodes if the group is optional + if matches > 0 && matches < min { return out, Errorf(t.Pos, "sub-expression %s must match at least once", g) } // The idea here is that something like "a"? is a successful match and that parsing should proceed.