blob: 17f450056292efcac71bd168d7349805f5295a67 [file] [edit]
// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package parser
import (
"fmt"
"strings"
"testing"
"cel.dev/cel-go/common"
"cel.dev/cel-go/common/ast"
"cel.dev/cel-go/common/debug"
"cel.dev/cel-go/test"
)
var prattTestCases = []testInfo{
// Constants
{
I: `42`,
P: `42^#1:*expr.Constant_Int64Value#`,
},
{
I: `0x2A`,
P: `42^#1:*expr.Constant_Int64Value#`,
},
{
I: `42u`,
P: `42u^#1:*expr.Constant_Uint64Value#`,
},
{
I: `0x2Au`,
P: `42u^#1:*expr.Constant_Uint64Value#`,
},
{
I: `3.14`,
P: `3.14^#1:*expr.Constant_DoubleValue#`,
},
{
I: `-42`,
P: `-42^#1:*expr.Constant_Int64Value#`,
},
{
I: `-3.14`,
P: `-3.14^#1:*expr.Constant_DoubleValue#`,
},
{
I: `"hello world"`,
P: `"hello world"^#1:*expr.Constant_StringValue#`,
},
{
I: `b"bytes"`,
P: `b"bytes"^#1:*expr.Constant_BytesValue#`,
},
{
I: `true`,
P: `true^#1:*expr.Constant_BoolValue#`,
},
{
I: `false`,
P: `false^#1:*expr.Constant_BoolValue#`,
},
{
I: `null`,
P: `null^#1:*expr.Constant_NullValue#`,
},
{
I: `9223372036854775807`,
P: `9223372036854775807^#1:*expr.Constant_Int64Value#`,
},
{
I: `-9223372036854775808`,
P: `-9223372036854775808^#1:*expr.Constant_Int64Value#`,
},
{
I: `-0x1A`,
P: `-26^#1:*expr.Constant_Int64Value#`,
},
{
I: `-0X1a`,
P: `-26^#1:*expr.Constant_Int64Value#`,
},
{
I: `-0x8000000000000000`,
P: `-9223372036854775808^#1:*expr.Constant_Int64Value#`,
},
{
I: `0u`,
P: `0u^#1:*expr.Constant_Uint64Value#`,
},
{
I: `-5.5e-3`,
P: `-0.0055^#1:*expr.Constant_DoubleValue#`,
},
{
I: "\"\u2764\"",
P: "\"❤\"^#1:*expr.Constant_StringValue#",
},
{
I: "\"\\a\\b\\f\\n\\r\\t\\v'\\\"\\\\\\? Legal escapes\"",
P: `"\a\b\f\n\r\t\v'\"\\? Legal escapes"^#1:*expr.Constant_StringValue#`,
},
// Identifiers and Parentheses
{
I: `a`,
P: `a^#1:*expr.Expr_IdentExpr#`,
},
{
I: `(a)`,
P: `a^#1:*expr.Expr_IdentExpr#`,
},
{
I: `((a))`,
P: `a^#1:*expr.Expr_IdentExpr#`,
},
// Unary operators
{
I: `!a`,
P: `!_(
a^#2:*expr.Expr_IdentExpr#
)^#1:*expr.Expr_CallExpr#`,
},
{
I: `!false`,
P: `!_(
false^#2:*expr.Constant_BoolValue#
)^#1:*expr.Expr_CallExpr#`,
},
{
I: `-x`,
P: `-_(
x^#2:*expr.Expr_IdentExpr#
)^#1:*expr.Expr_CallExpr#`,
},
{
I: `---a`,
P: `-_(
-_(
-_(
a^#4:*expr.Expr_IdentExpr#
)^#3:*expr.Expr_CallExpr#
)^#2:*expr.Expr_CallExpr#
)^#1:*expr.Expr_CallExpr#`,
},
{
I: `- -1`,
P: `-_(
-1^#2:*expr.Constant_Int64Value#
)^#1:*expr.Expr_CallExpr#`,
},
{
I: `4--4`,
P: `_-_(
4^#1:*expr.Constant_Int64Value#,
-4^#3:*expr.Constant_Int64Value#
)^#2:*expr.Expr_CallExpr#`,
},
{
I: `!!a`,
P: `!_(
!_(
a^#3:*expr.Expr_IdentExpr#
)^#2:*expr.Expr_CallExpr#
)^#1:*expr.Expr_CallExpr#`,
},
{
I: `-!true`,
P: `-_(
!_(
true^#3:*expr.Constant_BoolValue#
)^#2:*expr.Expr_CallExpr#
)^#1:*expr.Expr_CallExpr#`,
},
{
I: `-!-x`,
P: `-_(
!_(
-_(
x^#4:*expr.Expr_IdentExpr#
)^#3:*expr.Expr_CallExpr#
)^#2:*expr.Expr_CallExpr#
)^#1:*expr.Expr_CallExpr#`,
},
// Binary & Ternary operators
{
I: `1 + 2 * 3 - 4 / 2 % 3`,
P: `_-_(
_+_(
1^#1:*expr.Constant_Int64Value#,
_*_(
2^#3:*expr.Constant_Int64Value#,
3^#5:*expr.Constant_Int64Value#
)^#4:*expr.Expr_CallExpr#
)^#2:*expr.Expr_CallExpr#,
_%_(
_/_(
4^#7:*expr.Constant_Int64Value#,
2^#9:*expr.Constant_Int64Value#
)^#8:*expr.Expr_CallExpr#,
3^#11:*expr.Constant_Int64Value#
)^#10:*expr.Expr_CallExpr#
)^#6:*expr.Expr_CallExpr#`,
},
{
I: `a < 10 && b <= 20 || c > 30 && d >= 40 || e == 50 && f != 60`,
P: `_||_(
_||_(
_&&_(
_<_(
a^#1:*expr.Expr_IdentExpr#,
10^#3:*expr.Constant_Int64Value#
)^#2:*expr.Expr_CallExpr#,
_<=_(
b^#5:*expr.Expr_IdentExpr#,
20^#7:*expr.Constant_Int64Value#
)^#6:*expr.Expr_CallExpr#
)^#4:*expr.Expr_CallExpr#,
_&&_(
_>_(
c^#9:*expr.Expr_IdentExpr#,
30^#11:*expr.Constant_Int64Value#
)^#10:*expr.Expr_CallExpr#,
_>=_(
d^#13:*expr.Expr_IdentExpr#,
40^#15:*expr.Constant_Int64Value#
)^#14:*expr.Expr_CallExpr#
)^#12:*expr.Expr_CallExpr#
)^#8:*expr.Expr_CallExpr#,
_&&_(
_==_(
e^#17:*expr.Expr_IdentExpr#,
50^#19:*expr.Constant_Int64Value#
)^#18:*expr.Expr_CallExpr#,
_!=_(
f^#21:*expr.Expr_IdentExpr#,
60^#23:*expr.Constant_Int64Value#
)^#22:*expr.Expr_CallExpr#
)^#20:*expr.Expr_CallExpr#
)^#16:*expr.Expr_CallExpr#`,
},
{
I: `a && b && c && d`,
P: `_&&_(
_&&_(
a^#1:*expr.Expr_IdentExpr#,
b^#3:*expr.Expr_IdentExpr#
)^#2:*expr.Expr_CallExpr#,
_&&_(
c^#5:*expr.Expr_IdentExpr#,
d^#7:*expr.Expr_IdentExpr#
)^#6:*expr.Expr_CallExpr#
)^#4:*expr.Expr_CallExpr#`,
},
{
I: `a && b && c && d`,
P: `_&&_(
a^#1:*expr.Expr_IdentExpr#,
b^#3:*expr.Expr_IdentExpr#,
c^#5:*expr.Expr_IdentExpr#,
d^#7:*expr.Expr_IdentExpr#
)^#2:*expr.Expr_CallExpr#`,
Opts: []Option{EnableVariadicOperatorASTs(true)},
},
{
I: `a || b || c || d`,
P: `_||_(
_||_(
a^#1:*expr.Expr_IdentExpr#,
b^#3:*expr.Expr_IdentExpr#
)^#2:*expr.Expr_CallExpr#,
_||_(
c^#5:*expr.Expr_IdentExpr#,
d^#7:*expr.Expr_IdentExpr#
)^#6:*expr.Expr_CallExpr#
)^#4:*expr.Expr_CallExpr#`,
},
{
I: `a || b || c || d`,
P: `_||_(
a^#1:*expr.Expr_IdentExpr#,
b^#3:*expr.Expr_IdentExpr#,
c^#5:*expr.Expr_IdentExpr#,
d^#7:*expr.Expr_IdentExpr#
)^#2:*expr.Expr_CallExpr#`,
Opts: []Option{EnableVariadicOperatorASTs(true)},
},
{
I: `10 - 3 - 2`,
P: `_-_(
_-_(
10^#1:*expr.Constant_Int64Value#,
3^#3:*expr.Constant_Int64Value#
)^#2:*expr.Expr_CallExpr#,
2^#5:*expr.Constant_Int64Value#
)^#4:*expr.Expr_CallExpr#`,
},
{
I: `(((10 - 3) - 2))`,
P: `_-_(
_-_(
10^#1:*expr.Constant_Int64Value#,
3^#3:*expr.Constant_Int64Value#
)^#2:*expr.Expr_CallExpr#,
2^#5:*expr.Constant_Int64Value#
)^#4:*expr.Expr_CallExpr#`,
},
{
I: `x in [1, 2, 3]`,
P: `@in(
x^#1:*expr.Expr_IdentExpr#,
[
1^#4:*expr.Constant_Int64Value#,
2^#5:*expr.Constant_Int64Value#,
3^#6:*expr.Constant_Int64Value#
]^#3:*expr.Expr_ListExpr#
)^#2:*expr.Expr_CallExpr#`,
},
{
I: `a ? b : c`,
P: `_?_:_(
a^#1:*expr.Expr_IdentExpr#,
b^#3:*expr.Expr_IdentExpr#,
c^#4:*expr.Expr_IdentExpr#
)^#2:*expr.Expr_CallExpr#`,
},
{
I: `a ? b : c ? d : e`,
P: `_?_:_(
a^#1:*expr.Expr_IdentExpr#,
b^#3:*expr.Expr_IdentExpr#,
_?_:_(
c^#4:*expr.Expr_IdentExpr#,
d^#6:*expr.Expr_IdentExpr#,
e^#7:*expr.Expr_IdentExpr#
)^#5:*expr.Expr_CallExpr#
)^#2:*expr.Expr_CallExpr#`,
},
{
I: `(((1 + 2))) * (3 + 4)`,
P: `_*_(
_+_(
1^#1:*expr.Constant_Int64Value#,
2^#3:*expr.Constant_Int64Value#
)^#2:*expr.Expr_CallExpr#,
_+_(
3^#5:*expr.Constant_Int64Value#,
4^#7:*expr.Constant_Int64Value#
)^#6:*expr.Expr_CallExpr#
)^#4:*expr.Expr_CallExpr#`,
},
// Members, Selects, Indexing, and Calls
{
I: `a.b()`,
P: `a^#1:*expr.Expr_IdentExpr#.b()^#2:*expr.Expr_CallExpr#`,
},
{
I: `a.b(1)`,
P: `a^#1:*expr.Expr_IdentExpr#.b(
1^#3:*expr.Constant_Int64Value#
)^#2:*expr.Expr_CallExpr#`,
},
{
I: `a.b(1, 2)`,
P: `a^#1:*expr.Expr_IdentExpr#.b(
1^#3:*expr.Constant_Int64Value#,
2^#4:*expr.Constant_Int64Value#
)^#2:*expr.Expr_CallExpr#`,
},
{
I: `"foo".size()`,
P: `"foo"^#1:*expr.Constant_StringValue#.size()^#2:*expr.Expr_CallExpr#`,
},
{
I: `a.b.c`,
P: `a^#1:*expr.Expr_IdentExpr#.b^#2:*expr.Expr_SelectExpr#.c^#3:*expr.Expr_SelectExpr#`,
},
{
I: `a[0]`,
P: `_[_](
a^#1:*expr.Expr_IdentExpr#,
0^#3:*expr.Constant_Int64Value#
)^#2:*expr.Expr_CallExpr#`,
},
{
I: `[1, 3, 4][0]`,
P: `_[_](
[
1^#2:*expr.Constant_Int64Value#,
3^#3:*expr.Constant_Int64Value#,
4^#4:*expr.Constant_Int64Value#
]^#1:*expr.Expr_ListExpr#,
0^#6:*expr.Constant_Int64Value#
)^#5:*expr.Expr_CallExpr#`,
},
{
I: `a[b[c]]`,
P: `_[_](
a^#1:*expr.Expr_IdentExpr#,
_[_](
b^#3:*expr.Expr_IdentExpr#,
c^#5:*expr.Expr_IdentExpr#
)^#4:*expr.Expr_CallExpr#
)^#2:*expr.Expr_CallExpr#`,
},
{
I: `a()`,
P: `a()^#1:*expr.Expr_CallExpr#`,
},
{
I: `a(b)`,
P: `a(
b^#2:*expr.Expr_IdentExpr#
)^#1:*expr.Expr_CallExpr#`,
},
{
I: `func(1, 2, 3)`,
P: `func(
1^#2:*expr.Constant_Int64Value#,
2^#3:*expr.Constant_Int64Value#,
3^#4:*expr.Constant_Int64Value#
)^#1:*expr.Expr_CallExpr#`,
},
{
I: `.func(1, 2)`,
P: `.func(
1^#2:*expr.Constant_Int64Value#,
2^#3:*expr.Constant_Int64Value#
)^#1:*expr.Expr_CallExpr#`,
},
// Collections & Structs
{
I: `[1, 2, 3]`,
P: `[
1^#2:*expr.Constant_Int64Value#,
2^#3:*expr.Constant_Int64Value#,
3^#4:*expr.Constant_Int64Value#
]^#1:*expr.Expr_ListExpr#`,
},
{
I: `[1, 2, 3,]`,
P: `[
1^#2:*expr.Constant_Int64Value#,
2^#3:*expr.Constant_Int64Value#,
3^#4:*expr.Constant_Int64Value#
]^#1:*expr.Expr_ListExpr#`,
},
{
I: `[]`,
P: `[]^#1:*expr.Expr_ListExpr#`,
},
{
I: `{"a": 1, "b": 2}`,
P: `{
"a"^#2:*expr.Constant_StringValue#:1^#4:*expr.Constant_Int64Value#^#3:*expr.Expr_CreateStruct_Entry#,
"b"^#5:*expr.Constant_StringValue#:2^#7:*expr.Constant_Int64Value#^#6:*expr.Expr_CreateStruct_Entry#
}^#1:*expr.Expr_StructExpr#`,
},
{
I: `{foo: 5, bar: "xyz"}`,
P: `{
foo^#2:*expr.Expr_IdentExpr#:5^#4:*expr.Constant_Int64Value#^#3:*expr.Expr_CreateStruct_Entry#,
bar^#5:*expr.Expr_IdentExpr#:"xyz"^#7:*expr.Constant_StringValue#^#6:*expr.Expr_CreateStruct_Entry#
}^#1:*expr.Expr_StructExpr#`,
},
{
I: `{}`,
P: `{}^#1:*expr.Expr_StructExpr#`,
},
{
I: `google.protobuf.Empty{}`,
P: `google.protobuf.Empty{}^#3:*expr.Expr_StructExpr#`,
},
{
I: `foo{ a: b }`,
P: `foo{
a:b^#2:*expr.Expr_IdentExpr#^#1:*expr.Expr_CreateStruct_Entry#
}^#1:*expr.Expr_StructExpr#`,
},
{
I: `pkg.Msg{field1: "val", field2: 42}`,
P: `pkg.Msg{
field1:"val"^#3:*expr.Constant_StringValue#^#2:*expr.Expr_CreateStruct_Entry#,
field2:42^#5:*expr.Constant_Int64Value#^#4:*expr.Expr_CreateStruct_Entry#
}^#2:*expr.Expr_StructExpr#`,
},
{
I: `.pkg.Msg{field1: "val"}`,
P: `.pkg.Msg{
field1:"val"^#3:*expr.Constant_StringValue#^#2:*expr.Expr_CreateStruct_Entry#
}^#2:*expr.Expr_StructExpr#`,
},
// Macros
{
I: `has(a.b)`,
P: `a^#2:*expr.Expr_IdentExpr#.b~test-only~^#4:has#`,
},
{
I: `[1, 2, 3].all(x, x > 0)`,
P: `__comprehension__(
// Variable
x,
// Target
[
1^#2:*expr.Constant_Int64Value#,
2^#3:*expr.Constant_Int64Value#,
3^#4:*expr.Constant_Int64Value#
]^#1:*expr.Expr_ListExpr#,
// Accumulator
@result,
// Init
true^#10:*expr.Constant_BoolValue#,
// LoopCondition
@not_strictly_false(
@result^#11:*expr.Expr_IdentExpr#
)^#12:*expr.Expr_CallExpr#,
// LoopStep
_&&_(
@result^#13:*expr.Expr_IdentExpr#,
_>_(
x^#7:*expr.Expr_IdentExpr#,
0^#9:*expr.Constant_Int64Value#
)^#8:*expr.Expr_CallExpr#
)^#14:*expr.Expr_CallExpr#,
// Result
@result^#15:*expr.Expr_IdentExpr#)^#16:all#`,
},
{
I: `[1, 2, 3].exists(x, x == 2)`,
P: `__comprehension__(
// Variable
x,
// Target
[
1^#2:*expr.Constant_Int64Value#,
2^#3:*expr.Constant_Int64Value#,
3^#4:*expr.Constant_Int64Value#
]^#1:*expr.Expr_ListExpr#,
// Accumulator
@result,
// Init
false^#10:*expr.Constant_BoolValue#,
// LoopCondition
@not_strictly_false(
!_(
@result^#11:*expr.Expr_IdentExpr#
)^#12:*expr.Expr_CallExpr#
)^#13:*expr.Expr_CallExpr#,
// LoopStep
_||_(
@result^#14:*expr.Expr_IdentExpr#,
_==_(
x^#7:*expr.Expr_IdentExpr#,
2^#9:*expr.Constant_Int64Value#
)^#8:*expr.Expr_CallExpr#
)^#15:*expr.Expr_CallExpr#,
// Result
@result^#16:*expr.Expr_IdentExpr#)^#17:exists#`,
},
{
I: `[1, 2, 3].exists_one(x, x == 2)`,
P: `__comprehension__(
// Variable
x,
// Target
[
1^#2:*expr.Constant_Int64Value#,
2^#3:*expr.Constant_Int64Value#,
3^#4:*expr.Constant_Int64Value#
]^#1:*expr.Expr_ListExpr#,
// Accumulator
@result,
// Init
0^#10:*expr.Constant_Int64Value#,
// LoopCondition
true^#11:*expr.Constant_BoolValue#,
// LoopStep
_?_:_(
_==_(
x^#7:*expr.Expr_IdentExpr#,
2^#9:*expr.Constant_Int64Value#
)^#8:*expr.Expr_CallExpr#,
_+_(
@result^#12:*expr.Expr_IdentExpr#,
1^#13:*expr.Constant_Int64Value#
)^#14:*expr.Expr_CallExpr#,
@result^#15:*expr.Expr_IdentExpr#
)^#16:*expr.Expr_CallExpr#,
// Result
_==_(
@result^#17:*expr.Expr_IdentExpr#,
1^#18:*expr.Constant_Int64Value#
)^#19:*expr.Expr_CallExpr#)^#20:exists_one#`,
},
{
I: `[1, 2, 3].map(x, x * 2)`,
P: `__comprehension__(
// Variable
x,
// Target
[
1^#2:*expr.Constant_Int64Value#,
2^#3:*expr.Constant_Int64Value#,
3^#4:*expr.Constant_Int64Value#
]^#1:*expr.Expr_ListExpr#,
// Accumulator
@result,
// Init
[]^#10:*expr.Expr_ListExpr#,
// LoopCondition
true^#11:*expr.Constant_BoolValue#,
// LoopStep
_+_(
@result^#12:*expr.Expr_IdentExpr#,
[
_*_(
x^#7:*expr.Expr_IdentExpr#,
2^#9:*expr.Constant_Int64Value#
)^#8:*expr.Expr_CallExpr#
]^#13:*expr.Expr_ListExpr#
)^#14:*expr.Expr_CallExpr#,
// Result
@result^#15:*expr.Expr_IdentExpr#)^#16:map#`,
},
{
I: `[1, 2, 3].filter(x, x > 1)`,
P: `__comprehension__(
// Variable
x,
// Target
[
1^#2:*expr.Constant_Int64Value#,
2^#3:*expr.Constant_Int64Value#,
3^#4:*expr.Constant_Int64Value#
]^#1:*expr.Expr_ListExpr#,
// Accumulator
@result,
// Init
[]^#10:*expr.Expr_ListExpr#,
// LoopCondition
true^#11:*expr.Constant_BoolValue#,
// LoopStep
_?_:_(
_>_(
x^#7:*expr.Expr_IdentExpr#,
1^#9:*expr.Constant_Int64Value#
)^#8:*expr.Expr_CallExpr#,
_+_(
@result^#12:*expr.Expr_IdentExpr#,
[
x^#6:*expr.Expr_IdentExpr#
]^#13:*expr.Expr_ListExpr#
)^#14:*expr.Expr_CallExpr#,
@result^#15:*expr.Expr_IdentExpr#
)^#16:*expr.Expr_CallExpr#,
// Result
@result^#17:*expr.Expr_IdentExpr#)^#18:filter#`,
},
// Optional Syntax
{
I: `a.?b`,
P: `_?._(
a^#1:*expr.Expr_IdentExpr#,
"b"^#3:*expr.Constant_StringValue#
)^#2:*expr.Expr_CallExpr#`,
Opts: []Option{EnableOptionalSyntax(true)},
},
{
I: `a[?0]`,
P: `_[?_](
a^#1:*expr.Expr_IdentExpr#,
0^#3:*expr.Constant_Int64Value#
)^#2:*expr.Expr_CallExpr#`,
Opts: []Option{EnableOptionalSyntax(true)},
},
{
I: `[?a, b, ?c]`,
P: `[
a^#2:*expr.Expr_IdentExpr#,
b^#3:*expr.Expr_IdentExpr#,
c^#4:*expr.Expr_IdentExpr#
]^#1:*expr.Expr_ListExpr#`,
Opts: []Option{EnableOptionalSyntax(true)},
},
{
I: `{?a: 1, b: 2}`,
P: `{
?a^#2:*expr.Expr_IdentExpr#:1^#4:*expr.Constant_Int64Value#^#3:*expr.Expr_CreateStruct_Entry#,
b^#5:*expr.Expr_IdentExpr#:2^#7:*expr.Constant_Int64Value#^#6:*expr.Expr_CreateStruct_Entry#
}^#1:*expr.Expr_StructExpr#`,
Opts: []Option{EnableOptionalSyntax(true)},
},
{
I: `pkg.Msg{?field: 42}`,
P: `pkg.Msg{
?field:42^#3:*expr.Constant_Int64Value#^#2:*expr.Expr_CreateStruct_Entry#
}^#2:*expr.Expr_StructExpr#`,
Opts: []Option{EnableOptionalSyntax(true)},
},
// Escaped Identifier Syntax
{
I: "msg.`field-name`",
P: `msg^#1:*expr.Expr_IdentExpr#.field-name^#2:*expr.Expr_SelectExpr#`,
Opts: []Option{EnableIdentEscapeSyntax(true)},
},
{
I: "msg.`a.b.c`",
P: `msg^#1:*expr.Expr_IdentExpr#.a.b.c^#2:*expr.Expr_SelectExpr#`,
Opts: []Option{EnableIdentEscapeSyntax(true)},
},
{
I: "msg.`field name`",
P: `msg^#1:*expr.Expr_IdentExpr#.field name^#2:*expr.Expr_SelectExpr#`,
Opts: []Option{EnableIdentEscapeSyntax(true)},
},
{
I: "Msg{`field-1`: 42}",
P: `Msg{
field-1:42^#2:*expr.Constant_Int64Value#^#1:*expr.Expr_CreateStruct_Entry#
}^#1:*expr.Expr_StructExpr#`,
Opts: []Option{EnableIdentEscapeSyntax(true)},
},
// Error Cases
{
I: "(1 + 2",
E: "ERROR: <input>:1:7: expected ')'\n" +
" | (1 + 2\n" +
" | ......^",
},
{
I: "[1, 2",
E: "ERROR: <input>:1:6: expected ']'\n" +
" | [1, 2\n" +
" | .....^",
},
{
I: "{1: 2",
E: "ERROR: <input>:1:6: expected '}'\n" +
" | {1: 2\n" +
" | .....^",
},
{
I: "a ? b",
E: "ERROR: <input>:1:6: expected ':' in conditional expression\n" +
" | a ? b\n" +
" | .....^",
},
{
I: "1 + 2 3",
E: "ERROR: <input>:1:7: Syntax error: mismatched input '3' expecting <EOF>\n" +
" | 1 + 2 3\n" +
" | ......^",
},
{
I: "0xFFFFFFFFFFFFFFFFF",
E: "ERROR: <input>:1:1: invalid int literal\n" +
" | 0xFFFFFFFFFFFFFFFFF\n" +
" | ^",
},
{
I: "0xFFFFFFFFFFFFFFFFFu",
E: "ERROR: <input>:1:1: invalid uint literal\n" +
" | 0xFFFFFFFFFFFFFFFFFu\n" +
" | ^",
},
{
I: "1.99e90000009",
E: "ERROR: <input>:1:1: invalid double literal\n" +
" | 1.99e90000009\n" +
" | ^",
},
{
I: "as",
E: "ERROR: <input>:1:1: reserved identifier: as\n" +
" | as\n" +
" | ^",
},
{
I: "msg.`ident` + msg.`other`",
E: "ERROR: <input>:1:5: unsupported syntax '`'\n" +
" | msg.`ident` + msg.`other`\n" +
" | ....^\n" +
"ERROR: <input>:1:19: unsupported syntax '`'\n" +
" | msg.`ident` + msg.`other`\n" +
" | ..................^",
Opts: []Option{EnableIdentEscapeSyntax(false)},
},
{
I: "a.?b",
E: "ERROR: <input>:1:2: unsupported syntax '.?'\n" +
" | a.?b\n" +
" | .^",
},
{
I: "has(m)",
E: "ERROR: <input>:1:5: invalid argument to has() macro\n" +
" | has(m)\n" +
" | ....^",
Opts: []Option{Macros(AllMacros...)},
},
{
I: "[1, 2].all(1 + 2, true)",
E: "ERROR: <input>:1:14: argument must be a simple name\n" +
" | [1, 2].all(1 + 2, true)\n" +
" | .............^",
Opts: []Option{Macros(AllMacros...)},
},
{
I: "[1, 2].all(__result__, true)",
E: "ERROR: <input>:1:12: iteration variable overwrites accumulator variable\n" +
" | [1, 2].all(__result__, true)\n" +
" | ...........^",
Opts: []Option{Macros(AllMacros...)},
},
{
I: "1{}",
E: "ERROR: <input>:1:2: Syntax error: mismatched input '{' expecting <EOF>\n" +
" | 1{}\n" +
" | .^",
},
{
I: "a.",
E: "ERROR: <input>:1:3: expected identifier after '.'\n" +
" | a.\n" +
" | ..^",
},
{
I: ". *",
E: "ERROR: <input>:1:3: expected identifier\n" +
" | . *\n" +
" | ..^",
},
{
I: ".as",
E: "ERROR: <input>:1:2: reserved identifier: as\n" +
" | .as\n" +
" | .^",
},
{
I: "* 2",
E: "ERROR: <input>:1:1: unexpected token\n" +
" | * 2\n" +
" | ^\n" +
"ERROR: <input>:1:3: Syntax error: mismatched input '2' expecting <EOF>\n" +
" | * 2\n" +
" | ..^",
},
{
I: "{'k' 'v'}",
E: "ERROR: <input>:1:6: expected ':' in map entry\n" +
" | {'k' 'v'}\n" +
" | .....^",
},
{
I: "Msg{1: 2}",
E: "ERROR: <input>:1:5: expected struct field name\n" +
" | Msg{1: 2}\n" +
" | ....^",
},
{
I: "Msg{f 10}",
E: "ERROR: <input>:1:7: expected ':' in struct field\n" +
" | Msg{f 10}\n" +
" | ......^",
},
{
I: "Msg{f: 10",
E: "ERROR: <input>:1:10: expected '}'\n" +
" | Msg{f: 10\n" +
" | .........^",
},
{
I: "f(1, 2",
E: "ERROR: <input>:1:7: Syntax error: mismatched input <EOF> expecting ')'\n" +
" | f(1, 2\n" +
" | ......^",
},
{
I: "1e",
E: "ERROR: <input>:1:1: floating point literal missing digits after exponent separator\n" +
" | 1e\n" +
" | ^",
},
{
I: "\"unterminated",
E: "ERROR: <input>:1:1: unterminated string literal\n" +
" | \"unterminated\n" +
" | ^",
},
{
I: "b\"unterminated",
E: "ERROR: <input>:1:1: unterminated bytes literal\n" +
" | b\"unterminated\n" +
" | ^",
},
{
I: "a.`foo`()",
E: "ERROR: <input>:1:3: unexpected quoted identifier\n" +
" | a.`foo`()\n" +
" | ..^",
Opts: []Option{EnableIdentEscapeSyntax(true)},
},
{
I: "`foo`",
E: "ERROR: <input>:1:1: unexpected quoted identifier\n" +
" | `foo`\n" +
" | ^",
Opts: []Option{EnableIdentEscapeSyntax(true)},
},
{
I: "a.`b@c`",
E: "ERROR: <input>:1:3: unexpected quoted identifier\n" +
" | a.`b@c`\n" +
" | ..^",
Opts: []Option{EnableIdentEscapeSyntax(true)},
},
{
I: "a.``",
E: "ERROR: <input>:1:3: unexpected quoted identifier\n" +
" | a.``\n" +
" | ..^",
Opts: []Option{EnableIdentEscapeSyntax(true)},
},
{
I: "`foo",
E: "ERROR: <input>:1:1: unterminated quoted identifier\n" +
" | `foo\n" +
" | ^",
Opts: []Option{EnableIdentEscapeSyntax(true)},
},
{
I: "-0x8000000000000001",
E: "ERROR: <input>:1:2: invalid int literal\n" +
" | -0x8000000000000001\n" +
" | .^",
},
{
I: "-9223372036854775809",
E: "ERROR: <input>:1:2: invalid int literal\n" +
" | -9223372036854775809\n" +
" | .^",
},
{
I: "a[?0]",
E: "ERROR: <input>:1:2: unsupported syntax '?'\n" +
" | a[?0]\n" +
" | .^",
Opts: []Option{EnableOptionalSyntax(false)},
},
{
I: "[?1]",
E: "ERROR: <input>:1:2: unsupported syntax '?'\n" +
" | [?1]\n" +
" | .^",
Opts: []Option{EnableOptionalSyntax(false)},
},
{
I: "{?'k': 'v'}",
E: "ERROR: <input>:1:2: unsupported syntax '?'\n" +
" | {?'k': 'v'}\n" +
" | .^",
Opts: []Option{EnableOptionalSyntax(false)},
},
{
I: "Msg{?f: 1}",
E: "ERROR: <input>:1:5: unsupported syntax '?'\n" +
" | Msg{?f: 1}\n" +
" | ....^",
Opts: []Option{EnableOptionalSyntax(false)},
},
{
I: "a.?`foo`",
E: "ERROR: <input>:1:4: unsupported syntax '`'\n" +
" | a.?`foo`\n" +
" | ...^",
Opts: []Option{EnableOptionalSyntax(true), EnableIdentEscapeSyntax(false)},
},
}
func parse(source common.Source, opts ...Option) (*ast.AST, *common.Errors) {
p, err := NewPrattParser(opts...)
if err != nil {
panic(err)
}
return p.Parse(source)
}
func TestPrattParser(t *testing.T) {
for i, tst := range prattTestCases {
name := fmt.Sprintf("%d %s", i, tst.I)
// Local variable required as the closure will reference the value for the last
// 'tst' value rather than the local 'tc' instance declared within the loop.
tc := tst
t.Run(name, func(t *testing.T) {
t.Parallel()
opts := tc.Opts
if len(opts) == 0 {
opts = []Option{Macros(AllMacros...), PopulateMacroCalls(true)}
}
src := common.NewTextSource(tc.I)
parsed, errors := parse(src, opts...)
if len(errors.GetErrors()) > 0 {
actualErr := errors.ToDisplayString()
if tc.E == "" {
t.Fatalf("Unexpected errors: %v", actualErr)
} else if !test.Compare(actualErr, tc.E) {
t.Fatal(test.DiffMessage("Error mismatch", actualErr, tc.E))
}
return
} else if tc.E != "" {
t.Fatalf("Expected error not thrown: '%s'", tc.E)
}
failureDisplayMethod := fmt.Sprintf("Parse(\"%s\")", tc.I)
actualWithKind := debug.ToAdornedDebugString(parsed.Expr(), &kindAndIDAdorner{parsed.SourceInfo()})
if !test.Compare(actualWithKind, tc.P) {
t.Fatal(test.DiffMessage(fmt.Sprintf("Structure - %s", failureDisplayMethod), actualWithKind, tc.P))
}
})
}
}
func TestPrattParserSourceInfoPositions(t *testing.T) {
src := common.NewTextSource("a + b")
p, err := NewPrattParser()
if err != nil {
t.Fatalf("NewPrattParser() failed: %v", err)
}
parsed, errs := p.Parse(src)
if len(errs.GetErrors()) > 0 {
t.Fatalf("Parse() failed: %s", errs.ToDisplayString())
}
sourceInfo := parsed.SourceInfo()
root := parsed.Expr()
if sourceInfo.GetStartLocation(root.ID()).Column() != 2 {
t.Errorf("expected root column 2, got %d", sourceInfo.GetStartLocation(root.ID()).Column())
}
args := root.AsCall().Args()
if len(args) != 2 {
t.Fatalf("expected 2 args, got %d", len(args))
}
if sourceInfo.GetStartLocation(args[0].ID()).Column() != 0 {
t.Errorf("expected arg[0] column 0, got %d", sourceInfo.GetStartLocation(args[0].ID()).Column())
}
if sourceInfo.GetStartLocation(args[1].ID()).Column() != 4 {
t.Errorf("expected arg[1] column 4, got %d", sourceInfo.GetStartLocation(args[1].ID()).Column())
}
}
func TestPrattParserRecursionDepth(t *testing.T) {
t.Run("DeeplyNestedBracketsLimitExceeded", func(t *testing.T) {
p, err := NewPrattParser(MaxRecursionDepth(5))
if err != nil {
t.Fatalf("NewPrattParser() failed: %v", err)
}
_, errs := p.Parse(common.NewTextSource("[[[[[[1]]]]]]"))
if len(errs.GetErrors()) == 0 {
t.Errorf("expected recursion limit error, got none")
}
})
t.Run("IgnoreExtraParens", func(t *testing.T) {
p, err := NewPrattParser(MaxRecursionDepth(1))
if err != nil {
t.Fatalf("NewPrattParser() failed: %v", err)
}
_, errs := p.Parse(common.NewTextSource("((((1))))"))
if len(errs.GetErrors()) > 0 {
t.Errorf("unexpected error: %s", errs.ToDisplayString())
}
})
t.Run("DeeplyNestedParens1000", func(t *testing.T) {
p, err := NewPrattParser(MaxRecursionDepth(1))
if err != nil {
t.Fatalf("NewPrattParser() failed: %v", err)
}
expr1 := strings.Repeat("(", 1000) + "42" + strings.Repeat(")", 1000)
_, errs := p.Parse(common.NewTextSource(expr1))
if len(errs.GetErrors()) > 0 {
t.Errorf("unexpected error on 1000 parens literal: %s", errs.ToDisplayString())
}
expr2 := strings.Repeat("(", 1000) + "1 + 2" + strings.Repeat(")", 1000)
_, errs = p.Parse(common.NewTextSource(expr2))
if len(errs.GetErrors()) > 0 {
t.Errorf("unexpected error on 1000 parens binary: %s", errs.ToDisplayString())
}
})
t.Run("SequentialScopesDoNotAccumulateDepth", func(t *testing.T) {
p, err := NewPrattParser(MaxRecursionDepth(2))
if err != nil {
t.Fatalf("NewPrattParser() failed: %v", err)
}
_, errs := p.Parse(common.NewTextSource("[1] + [2] + [3]"))
if len(errs.GetErrors()) > 0 {
t.Errorf("unexpected error on sequential scopes: %s", errs.ToDisplayString())
}
})
}
func TestPrattParserMacroCalls(t *testing.T) {
t.Run("DisabledByDefault", func(t *testing.T) {
p, err := NewPrattParser(Macros(AllMacros...), PopulateMacroCalls(false))
if err != nil {
t.Fatalf("NewPrattParser() failed: %v", err)
}
parsed, errs := p.Parse(common.NewTextSource("has(a.b)"))
if len(errs.GetErrors()) > 0 {
t.Fatalf("unexpected error: %s", errs.ToDisplayString())
}
if len(parsed.SourceInfo().MacroCalls()) != 0 {
t.Errorf("expected 0 macro calls, got %d", len(parsed.SourceInfo().MacroCalls()))
}
})
t.Run("GlobalMacroCallRecorded", func(t *testing.T) {
p, err := NewPrattParser(Macros(AllMacros...), PopulateMacroCalls(true))
if err != nil {
t.Fatalf("NewPrattParser() failed: %v", err)
}
parsed, errs := p.Parse(common.NewTextSource("has(a.b)"))
if len(errs.GetErrors()) > 0 {
t.Fatalf("unexpected error: %s", errs.ToDisplayString())
}
macroCalls := parsed.SourceInfo().MacroCalls()
if len(macroCalls) != 1 {
t.Fatalf("expected 1 macro call, got %d", len(macroCalls))
}
})
t.Run("ReceiverMacroCallRecorded", func(t *testing.T) {
p, err := NewPrattParser(Macros(AllMacros...), PopulateMacroCalls(true))
if err != nil {
t.Fatalf("NewPrattParser() failed: %v", err)
}
parsed, errs := p.Parse(common.NewTextSource("[1, 2].exists(x, x > 0)"))
if len(errs.GetErrors()) > 0 {
t.Fatalf("unexpected error: %s", errs.ToDisplayString())
}
macroCalls := parsed.SourceInfo().MacroCalls()
if len(macroCalls) != 1 {
t.Fatalf("expected 1 macro call, got %d", len(macroCalls))
}
})
t.Run("NestedMacroCallsRecorded", func(t *testing.T) {
p, err := NewPrattParser(Macros(AllMacros...), PopulateMacroCalls(true))
if err != nil {
t.Fatalf("NewPrattParser() failed: %v", err)
}
parsed, errs := p.Parse(common.NewTextSource("[1, 2].all(x, has(x.b))"))
if len(errs.GetErrors()) > 0 {
t.Fatalf("unexpected error: %s", errs.ToDisplayString())
}
macroCalls := parsed.SourceInfo().MacroCalls()
if len(macroCalls) != 2 {
t.Fatalf("expected 2 macro calls, got %d", len(macroCalls))
}
})
}
func TestPrattParserErrorRecoveryLimits(t *testing.T) {
t.Run("LimitZero", func(t *testing.T) {
p, err := NewPrattParser(ErrorRecoveryLimit(0))
if err != nil {
t.Fatalf("NewPrattParser() failed: %v", err)
}
_, errs := p.Parse(common.NewTextSource("......"))
if len(errs.GetErrors()) == 0 {
t.Errorf("expected error recovery limit error, got none")
}
})
t.Run("LimitOne", func(t *testing.T) {
p, err := NewPrattParser(ErrorRecoveryLimit(1))
if err != nil {
t.Fatalf("NewPrattParser() failed: %v", err)
}
_, errs := p.Parse(common.NewTextSource("......"))
if len(errs.GetErrors()) == 0 {
t.Errorf("expected error recovery limit error, got none")
}
})
}
func TestPrattParserExpressionSizeCodePointLimit(t *testing.T) {
p, err := NewPrattParser(Macros(AllMacros...), ExpressionSizeCodePointLimit(2))
if err != nil {
t.Fatal(err)
}
src := common.NewTextSource("foo")
_, errs := p.Parse(src)
if got, want := len(errs.GetErrors()), 1; got != want {
t.Fatalf("got %d errors, want %d errors: %s", got, want, errs.ToDisplayString())
}
if got, want := errs.GetErrors()[0].Message, "expression code point size exceeds limit: size: 3, limit 2"; got != want {
t.Fatalf("got %q, want %q: %s", got, want, errs.GetErrors()[0].ToDisplayString(src))
}
}
func BenchmarkParsers(b *testing.B) {
exprs := []string{
`42`,
`a > 5 && b < 10 || c == "xyz"`,
`[1, 2, 3].all(x, x > 0) && [4, 5, 6].exists(y, y == 5)`,
`pkg.Msg{field1: "value", field2: 123, list_field: [1, 2, 3], map_field: {"a": true, "b": false}}`,
`a.b.c.d.e.f(1, 2, [3, ?4], {?5: 6}) ? (x + y * z - w / v) : (!p && !q || r.s)`,
}
antlrParser, _ := NewParser(Macros(AllMacros...), PopulateMacroCalls(true), EnableOptionalSyntax(true))
prattParser, _ := NewPrattParser(Macros(AllMacros...), PopulateMacroCalls(true), EnableOptionalSyntax(true))
for _, expr := range exprs {
src := common.NewTextSource(expr)
b.Run("ANTLR/"+expr[:min(len(expr), 20)], func(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = antlrParser.Parse(src)
}
})
b.Run("Pratt/"+expr[:min(len(expr), 20)], func(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = prattParser.Parse(src)
}
})
}
}
func min(a, b int) int {
if a < b {
return a
}
return b
}