blob: d7696c23a144defa654e2c9b8d07297ccfd15a5f [file] [log] [blame]
// Copyright 2018 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"
"reflect"
"testing"
"github.com/google/cel-go/common"
"github.com/google/cel-go/common/debug"
"github.com/google/cel-go/test"
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
)
var testCases = []testInfo{
{
I: `"A"`,
P: `"A"^#1:*expr.Constant_StringValue#`,
},
{
I: `true`,
P: `true^#1:*expr.Constant_BoolValue#`,
},
{
I: `false`,
P: `false^#1:*expr.Constant_BoolValue#`,
},
{
I: `0`,
P: `0^#1:*expr.Constant_Int64Value#`,
},
{
I: `42`,
P: `42^#1:*expr.Constant_Int64Value#`,
},
{
I: `0u`,
P: `0u^#1:*expr.Constant_Uint64Value#`,
},
{
I: `23u`,
P: `23u^#1:*expr.Constant_Uint64Value#`,
},
{
I: `24u`,
P: `24u^#1:*expr.Constant_Uint64Value#`,
},
{
I: `-1`,
P: `-1^#1:*expr.Constant_Int64Value#`,
},
{
I: `4--4`,
P: `_-_(
4^#1:*expr.Constant_Int64Value#,
-4^#3:*expr.Constant_Int64Value#
)^#2:*expr.Expr_CallExpr#`,
},
{
I: `4--4.1`,
P: `_-_(
4^#1:*expr.Constant_Int64Value#,
-4.1^#3:*expr.Constant_DoubleValue#
)^#2:*expr.Expr_CallExpr#`,
},
{
I: `b"abc"`,
P: `b"abc"^#1:*expr.Constant_BytesValue#`,
},
{
I: `23.39`,
P: `23.39^#1:*expr.Constant_DoubleValue#`,
},
{
I: `!a`,
P: `!_(
a^#2:*expr.Expr_IdentExpr#
)^#1:*expr.Expr_CallExpr#`,
},
{
I: `null`,
P: `null^#1:*expr.Constant_NullValue#`,
},
{
I: `a`,
P: `a^#1:*expr.Expr_IdentExpr#`,
},
{
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`,
P: `_||_(
a^#1:*expr.Expr_IdentExpr#,
b^#2:*expr.Expr_IdentExpr#
)^#3:*expr.Expr_CallExpr#`,
},
{
I: `a || b || c || d || e || f `,
P: ` _||_(
_||_(
_||_(
a^#1:*expr.Expr_IdentExpr#,
b^#2:*expr.Expr_IdentExpr#
)^#3:*expr.Expr_CallExpr#,
c^#4:*expr.Expr_IdentExpr#
)^#5:*expr.Expr_CallExpr#,
_||_(
_||_(
d^#6:*expr.Expr_IdentExpr#,
e^#8:*expr.Expr_IdentExpr#
)^#9:*expr.Expr_CallExpr#,
f^#10:*expr.Expr_IdentExpr#
)^#11:*expr.Expr_CallExpr#
)^#7:*expr.Expr_CallExpr#`,
},
{
I: `a && b`,
P: `_&&_(
a^#1:*expr.Expr_IdentExpr#,
b^#2:*expr.Expr_IdentExpr#
)^#3:*expr.Expr_CallExpr#`,
},
{
I: `a && b && c && d && e && f && g`,
P: `_&&_(
_&&_(
_&&_(
a^#1:*expr.Expr_IdentExpr#,
b^#2:*expr.Expr_IdentExpr#
)^#3:*expr.Expr_CallExpr#,
_&&_(
c^#4:*expr.Expr_IdentExpr#,
d^#6:*expr.Expr_IdentExpr#
)^#7:*expr.Expr_CallExpr#
)^#5:*expr.Expr_CallExpr#,
_&&_(
_&&_(
e^#8:*expr.Expr_IdentExpr#,
f^#10:*expr.Expr_IdentExpr#
)^#11:*expr.Expr_CallExpr#,
g^#12:*expr.Expr_IdentExpr#
)^#13:*expr.Expr_CallExpr#
)^#9:*expr.Expr_CallExpr#`,
},
{
I: `a && b && c && d || e && f && g && h`,
P: `_||_(
_&&_(
_&&_(
a^#1:*expr.Expr_IdentExpr#,
b^#2:*expr.Expr_IdentExpr#
)^#3:*expr.Expr_CallExpr#,
_&&_(
c^#4:*expr.Expr_IdentExpr#,
d^#6:*expr.Expr_IdentExpr#
)^#7:*expr.Expr_CallExpr#
)^#5:*expr.Expr_CallExpr#,
_&&_(
_&&_(
e^#8:*expr.Expr_IdentExpr#,
f^#9:*expr.Expr_IdentExpr#
)^#10:*expr.Expr_CallExpr#,
_&&_(
g^#11:*expr.Expr_IdentExpr#,
h^#13:*expr.Expr_IdentExpr#
)^#14:*expr.Expr_CallExpr#
)^#12:*expr.Expr_CallExpr#
)^#15:*expr.Expr_CallExpr#`,
},
{
I: `a + b`,
P: `_+_(
a^#1:*expr.Expr_IdentExpr#,
b^#3:*expr.Expr_IdentExpr#
)^#2:*expr.Expr_CallExpr#`,
},
{
I: `a - b`,
P: `_-_(
a^#1:*expr.Expr_IdentExpr#,
b^#3:*expr.Expr_IdentExpr#
)^#2:*expr.Expr_CallExpr#`,
},
{
I: `a * b`,
P: `_*_(
a^#1:*expr.Expr_IdentExpr#,
b^#3:*expr.Expr_IdentExpr#
)^#2:*expr.Expr_CallExpr#`,
},
{
I: `a / b`,
P: `_/_(
a^#1:*expr.Expr_IdentExpr#,
b^#3:*expr.Expr_IdentExpr#
)^#2:*expr.Expr_CallExpr#`,
},
{
I: `a % b`,
P: `_%_(
a^#1:*expr.Expr_IdentExpr#,
b^#3:*expr.Expr_IdentExpr#
)^#2:*expr.Expr_CallExpr#`,
},
{
I: `a in b`,
P: `@in(
a^#1:*expr.Expr_IdentExpr#,
b^#3:*expr.Expr_IdentExpr#
)^#2:*expr.Expr_CallExpr#`,
},
{
I: `a == b`,
P: `_==_(
a^#1:*expr.Expr_IdentExpr#,
b^#3:*expr.Expr_IdentExpr#
)^#2:*expr.Expr_CallExpr#`,
},
{
I: `a != b`,
P: ` _!=_(
a^#1:*expr.Expr_IdentExpr#,
b^#3:*expr.Expr_IdentExpr#
)^#2:*expr.Expr_CallExpr#`,
},
{
I: `a > b`,
P: `_>_(
a^#1:*expr.Expr_IdentExpr#,
b^#3:*expr.Expr_IdentExpr#
)^#2:*expr.Expr_CallExpr#`,
},
{
I: `a >= b`,
P: `_>=_(
a^#1:*expr.Expr_IdentExpr#,
b^#3:*expr.Expr_IdentExpr#
)^#2:*expr.Expr_CallExpr#`,
},
{
I: `a < b`,
P: `_<_(
a^#1:*expr.Expr_IdentExpr#,
b^#3:*expr.Expr_IdentExpr#
)^#2:*expr.Expr_CallExpr#`,
},
{
I: `a <= b`,
P: `_<=_(
a^#1:*expr.Expr_IdentExpr#,
b^#3:*expr.Expr_IdentExpr#
)^#2:*expr.Expr_CallExpr#`,
},
{
I: `a.b`,
P: `a^#1:*expr.Expr_IdentExpr#.b^#2:*expr.Expr_SelectExpr#`,
},
{
I: `a.b.c`,
P: `a^#1:*expr.Expr_IdentExpr#.b^#2:*expr.Expr_SelectExpr#.c^#3:*expr.Expr_SelectExpr#`,
},
{
I: `a[b]`,
P: `_[_](
a^#1:*expr.Expr_IdentExpr#,
b^#3:*expr.Expr_IdentExpr#
)^#2:*expr.Expr_CallExpr#`,
},
// TODO: This is an error.
//{
// I: `foo{ a=b, c=d }`,
// P: `
//
// `,
//},
{
I: `foo{ }`,
P: `foo{}^#2:*expr.Expr_StructExpr#`,
},
{
I: `foo{ a:b }`,
P: `foo{
a:b^#4:*expr.Expr_IdentExpr#^#3:*expr.Expr_CreateStruct_Entry#
}^#2:*expr.Expr_StructExpr#`,
},
{
I: `foo{ a:b, c:d }`,
P: `foo{
a:b^#4:*expr.Expr_IdentExpr#^#3:*expr.Expr_CreateStruct_Entry#,
c:d^#6:*expr.Expr_IdentExpr#^#5:*expr.Expr_CreateStruct_Entry#
}^#2:*expr.Expr_StructExpr#`,
},
{
I: `{}`,
P: `{}^#1:*expr.Expr_StructExpr#`,
},
{
I: `{a:b, c:d}`,
P: `{
a^#3:*expr.Expr_IdentExpr#:b^#4:*expr.Expr_IdentExpr#^#2:*expr.Expr_CreateStruct_Entry#,
c^#6:*expr.Expr_IdentExpr#:d^#7:*expr.Expr_IdentExpr#^#5:*expr.Expr_CreateStruct_Entry#
}^#1:*expr.Expr_StructExpr#`,
},
{
I: `[]`,
P: `[]^#1:*expr.Expr_ListExpr#`,
},
{
I: `[a]`,
P: `[
a^#2:*expr.Expr_IdentExpr#
]^#1:*expr.Expr_ListExpr#`,
},
{
I: `[a, b, c]`,
P: `[
a^#2:*expr.Expr_IdentExpr#,
b^#3:*expr.Expr_IdentExpr#,
c^#4:*expr.Expr_IdentExpr#
]^#1:*expr.Expr_ListExpr#`,
},
{
I: `(a)`,
P: `a^#1:*expr.Expr_IdentExpr#`,
},
{
I: `((a))`,
P: `a^#1:*expr.Expr_IdentExpr#`,
},
{
I: `a()`,
P: `a()^#1:*expr.Expr_CallExpr#`,
},
{
I: `a(b)`,
P: `a(
b^#2:*expr.Expr_IdentExpr#
)^#1:*expr.Expr_CallExpr#`,
},
{
I: `a(b, c)`,
P: `a(
b^#2:*expr.Expr_IdentExpr#,
c^#3:*expr.Expr_IdentExpr#
)^#1:*expr.Expr_CallExpr#`,
},
{
I: `a.b()`,
P: `a^#1:*expr.Expr_IdentExpr#.b()^#2:*expr.Expr_CallExpr#`,
},
{
I: `a.b(c)`,
P: `a^#1:*expr.Expr_IdentExpr#.b(
c^#3:*expr.Expr_IdentExpr#
)^#2:*expr.Expr_CallExpr#`,
L: `a^#1[1,0]#.b(
c^#3[1,4]#
)^#2[1,3]#`,
},
// Parse error tests
{
I: `*@a | b`,
E: `
ERROR: <input>:1:1: Syntax error: extraneous input '*' expecting {'[', '{', '(', '.', '-', '!', 'true', 'false', 'null', NUM_FLOAT, NUM_INT, NUM_UINT, STRING, BYTES, IDENTIFIER}
| *@a | b
| ^
ERROR: <input>:1:2: Syntax error: token recognition error at: '@'
| *@a | b
| .^
ERROR: <input>:1:5: Syntax error: token recognition error at: '| '
| *@a | b
| ....^
ERROR: <input>:1:7: Syntax error: extraneous input 'b' expecting <EOF>
| *@a | b
| ......^`,
},
{
I: `a | b`,
E: `
ERROR: <input>:1:3: Syntax error: token recognition error at: '| '
| a | b
| ..^
ERROR: <input>:1:5: Syntax error: extraneous input 'b' expecting <EOF>
| a | b
| ....^`,
},
// Macro tests
{
I: `has(m.f)`,
P: `m^#2:*expr.Expr_IdentExpr#.f~test-only~^#4:*expr.Expr_SelectExpr#`,
L: `m^#2[1,4]#.f~test-only~^#4[1,3]#`,
},
{
I: `m.exists_one(v, f)`,
P: `__comprehension__(
// Variable
v,
// Target
m^#1:*expr.Expr_IdentExpr#,
// Accumulator
__result__,
// Init
0^#5:*expr.Constant_Int64Value#,
// LoopCondition
true^#7:*expr.Constant_BoolValue#,
// LoopStep
_?_:_(
f^#4:*expr.Expr_IdentExpr#,
_+_(
__result__^#8:*expr.Expr_IdentExpr#,
1^#6:*expr.Constant_Int64Value#
)^#9:*expr.Expr_CallExpr#,
__result__^#10:*expr.Expr_IdentExpr#
)^#11:*expr.Expr_CallExpr#,
// Result
_==_(
__result__^#12:*expr.Expr_IdentExpr#,
1^#6:*expr.Constant_Int64Value#
)^#13:*expr.Expr_CallExpr#)^#14:*expr.Expr_ComprehensionExpr#`,
},
{
I: `m.map(v, f)`,
P: `__comprehension__(
// Variable
v,
// Target
m^#1:*expr.Expr_IdentExpr#,
// Accumulator
__result__,
// Init
[]^#6:*expr.Expr_ListExpr#,
// LoopCondition
true^#7:*expr.Constant_BoolValue#,
// LoopStep
_+_(
__result__^#5:*expr.Expr_IdentExpr#,
[
f^#4:*expr.Expr_IdentExpr#
]^#8:*expr.Expr_ListExpr#
)^#9:*expr.Expr_CallExpr#,
// Result
__result__^#5:*expr.Expr_IdentExpr#)^#10:*expr.Expr_ComprehensionExpr#`,
},
{
I: `m.map(v, p, f)`,
P: `__comprehension__(
// Variable
v,
// Target
m^#1:*expr.Expr_IdentExpr#,
// Accumulator
__result__,
// Init
[]^#7:*expr.Expr_ListExpr#,
// LoopCondition
true^#8:*expr.Constant_BoolValue#,
// LoopStep
_?_:_(
p^#4:*expr.Expr_IdentExpr#,
_+_(
__result__^#6:*expr.Expr_IdentExpr#,
[
f^#5:*expr.Expr_IdentExpr#
]^#9:*expr.Expr_ListExpr#
)^#10:*expr.Expr_CallExpr#,
__result__^#6:*expr.Expr_IdentExpr#
)^#11:*expr.Expr_CallExpr#,
// Result
__result__^#6:*expr.Expr_IdentExpr#)^#12:*expr.Expr_ComprehensionExpr#`,
},
{
I: `m.filter(v, p)`,
P: `__comprehension__(
// Variable
v,
// Target
m^#1:*expr.Expr_IdentExpr#,
// Accumulator
__result__,
// Init
[]^#6:*expr.Expr_ListExpr#,
// LoopCondition
true^#7:*expr.Constant_BoolValue#,
// LoopStep
_?_:_(
p^#4:*expr.Expr_IdentExpr#,
_+_(
__result__^#5:*expr.Expr_IdentExpr#,
[
v^#3:*expr.Expr_IdentExpr#
]^#8:*expr.Expr_ListExpr#
)^#9:*expr.Expr_CallExpr#,
__result__^#5:*expr.Expr_IdentExpr#
)^#10:*expr.Expr_CallExpr#,
// Result
__result__^#5:*expr.Expr_IdentExpr#)^#11:*expr.Expr_ComprehensionExpr#`,
},
// Tests from C++ parser
{
I: "x * 2",
P: `_*_(
x^#1:*expr.Expr_IdentExpr#,
2^#3:*expr.Constant_Int64Value#
)^#2:*expr.Expr_CallExpr#`,
},
{
I: "x * 2u",
P: `_*_(
x^#1:*expr.Expr_IdentExpr#,
2u^#3:*expr.Constant_Uint64Value#
)^#2:*expr.Expr_CallExpr#`,
},
{
I: "x * 2.0",
P: `_*_(
x^#1:*expr.Expr_IdentExpr#,
2^#3:*expr.Constant_DoubleValue#
)^#2:*expr.Expr_CallExpr#`,
},
{
I: `"\u2764"`,
P: "\"\u2764\"^#1:*expr.Constant_StringValue#",
},
{
I: "\"\u2764\"",
P: "\"\u2764\"^#1:*expr.Constant_StringValue#",
},
{
I: `! false`,
P: `!_(
false^#2:*expr.Constant_BoolValue#
)^#1:*expr.Expr_CallExpr#`,
},
{
I: `-a`,
P: `-_(
a^#2:*expr.Expr_IdentExpr#
)^#1:*expr.Expr_CallExpr#`,
},
{
I: `a.b(5)`,
P: `a^#1:*expr.Expr_IdentExpr#.b(
5^#3:*expr.Constant_Int64Value#
)^#2:*expr.Expr_CallExpr#`,
},
{
I: `a[3]`,
P: `_[_](
a^#1:*expr.Expr_IdentExpr#,
3^#3:*expr.Constant_Int64Value#
)^#2:*expr.Expr_CallExpr#`,
},
{
I: `SomeMessage{foo: 5, bar: "xyz"}`,
P: `SomeMessage{
foo:5^#4:*expr.Constant_Int64Value#^#3:*expr.Expr_CreateStruct_Entry#,
bar:"xyz"^#6:*expr.Constant_StringValue#^#5:*expr.Expr_CreateStruct_Entry#
}^#2:*expr.Expr_StructExpr#`,
},
{
I: `[3, 4, 5]`,
P: `[
3^#2:*expr.Constant_Int64Value#,
4^#3:*expr.Constant_Int64Value#,
5^#4:*expr.Constant_Int64Value#
]^#1:*expr.Expr_ListExpr#`,
},
{
I: `[3, 4, 5,]`,
P: `[
3^#2:*expr.Constant_Int64Value#,
4^#3:*expr.Constant_Int64Value#,
5^#4:*expr.Constant_Int64Value#
]^#1:*expr.Expr_ListExpr#`,
},
{
I: `{foo: 5, bar: "xyz"}`,
P: `{
foo^#3:*expr.Expr_IdentExpr#:5^#4:*expr.Constant_Int64Value#^#2:*expr.Expr_CreateStruct_Entry#,
bar^#6:*expr.Expr_IdentExpr#:"xyz"^#7:*expr.Constant_StringValue#^#5:*expr.Expr_CreateStruct_Entry#
}^#1:*expr.Expr_StructExpr#`,
},
{
I: `{foo: 5, bar: "xyz", }`,
P: `{
foo^#3:*expr.Expr_IdentExpr#:5^#4:*expr.Constant_Int64Value#^#2:*expr.Expr_CreateStruct_Entry#,
bar^#6:*expr.Expr_IdentExpr#:"xyz"^#7:*expr.Constant_StringValue#^#5:*expr.Expr_CreateStruct_Entry#
}^#1:*expr.Expr_StructExpr#`,
},
{
I: `a > 5 && a < 10`,
P: `_&&_(
_>_(
a^#1:*expr.Expr_IdentExpr#,
5^#3:*expr.Constant_Int64Value#
)^#2:*expr.Expr_CallExpr#,
_<_(
a^#4:*expr.Expr_IdentExpr#,
10^#6:*expr.Constant_Int64Value#
)^#5:*expr.Expr_CallExpr#
)^#7:*expr.Expr_CallExpr#`,
},
{
I: `a < 5 || a > 10`,
P: `_||_(
_<_(
a^#1:*expr.Expr_IdentExpr#,
5^#3:*expr.Constant_Int64Value#
)^#2:*expr.Expr_CallExpr#,
_>_(
a^#4:*expr.Expr_IdentExpr#,
10^#6:*expr.Constant_Int64Value#
)^#5:*expr.Expr_CallExpr#
)^#7:*expr.Expr_CallExpr#`,
},
{
I: `{`,
E: `ERROR: <input>:1:2: Syntax error: mismatched input '<EOF>' expecting {'[', '{', '}', '(', '.', ',', '-', '!', 'true', 'false', 'null', NUM_FLOAT, NUM_INT, NUM_UINT, STRING, BYTES, IDENTIFIER}
| {
| .^`,
},
// Tests from Java parser
{
I: `[] + [1,2,3,] + [4]`,
P: `_+_(
_+_(
[]^#1:*expr.Expr_ListExpr#,
[
1^#4:*expr.Constant_Int64Value#,
2^#5:*expr.Constant_Int64Value#,
3^#6:*expr.Constant_Int64Value#
]^#3:*expr.Expr_ListExpr#
)^#2:*expr.Expr_CallExpr#,
[
4^#9:*expr.Constant_Int64Value#
]^#8:*expr.Expr_ListExpr#
)^#7:*expr.Expr_CallExpr#`,
},
{
I: `{1:2u, 2:3u}`,
P: `{
1^#3:*expr.Constant_Int64Value#:2u^#4:*expr.Constant_Uint64Value#^#2:*expr.Expr_CreateStruct_Entry#,
2^#6:*expr.Constant_Int64Value#:3u^#7:*expr.Constant_Uint64Value#^#5:*expr.Expr_CreateStruct_Entry#
}^#1:*expr.Expr_StructExpr#`,
},
{
I: `TestAllTypes{single_int32: 1, single_int64: 2}`,
P: `TestAllTypes{
single_int32:1^#4:*expr.Constant_Int64Value#^#3:*expr.Expr_CreateStruct_Entry#,
single_int64:2^#6:*expr.Constant_Int64Value#^#5:*expr.Expr_CreateStruct_Entry#
}^#2:*expr.Expr_StructExpr#`,
},
{
I: `TestAllTypes(){single_int32: 1, single_int64: 2}`,
E: `
ERROR: <input>:1:13: expected a qualified name
| TestAllTypes(){single_int32: 1, single_int64: 2}
| ............^
`,
},
{
I: `size(x) == x.size()`,
P: `_==_(
size(
x^#2:*expr.Expr_IdentExpr#
)^#1:*expr.Expr_CallExpr#,
x^#4:*expr.Expr_IdentExpr#.size()^#5:*expr.Expr_CallExpr#
)^#3:*expr.Expr_CallExpr#`,
},
{
I: `1 + $`,
E: `
ERROR: <input>:1:5: Syntax error: token recognition error at: '$'
| 1 + $
| ....^
ERROR: <input>:1:6: Syntax error: mismatched input '<EOF>' expecting {'[', '{', '(', '.', '-', '!', 'true', 'false', 'null', NUM_FLOAT, NUM_INT, NUM_UINT, STRING, BYTES, IDENTIFIER}
| 1 + $
| .....^
`,
},
{
I: `1 + 2
3 +`,
E: `
ERROR: <input>:2:1: Syntax error: mismatched input '3' expecting <EOF>
| 3 +
| ^
`,
},
{
I: `"\""`,
P: `"\""^#1:*expr.Constant_StringValue#`,
},
{
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: `1.all(2, 3)`,
E: `
ERROR: <input>:1:7: argument must be a simple name
| 1.all(2, 3)
| ......^
`,
},
{
I: `x["a"].single_int32 == 23`,
P: `_==_(
_[_](
x^#1:*expr.Expr_IdentExpr#,
"a"^#3:*expr.Constant_StringValue#
)^#2:*expr.Expr_CallExpr#.single_int32^#4:*expr.Expr_SelectExpr#,
23^#6:*expr.Constant_Int64Value#
)^#5:*expr.Expr_CallExpr#`,
},
{
I: `x.single_nested_message != null`,
P: `_!=_(
x^#1:*expr.Expr_IdentExpr#.single_nested_message^#2:*expr.Expr_SelectExpr#,
null^#4:*expr.Constant_NullValue#
)^#3:*expr.Expr_CallExpr#`,
},
{
I: `false && !true || false ? 2 : 3`,
P: `_?_:_(
_||_(
_&&_(
false^#1:*expr.Constant_BoolValue#,
!_(
true^#3:*expr.Constant_BoolValue#
)^#2:*expr.Expr_CallExpr#
)^#4:*expr.Expr_CallExpr#,
false^#5:*expr.Constant_BoolValue#
)^#6:*expr.Expr_CallExpr#,
2^#8:*expr.Constant_Int64Value#,
3^#9:*expr.Constant_Int64Value#
)^#7:*expr.Expr_CallExpr#`,
},
{
I: `b"abc" + B"def"`,
P: `_+_(
b"abc"^#1:*expr.Constant_BytesValue#,
b"def"^#3:*expr.Constant_BytesValue#
)^#2:*expr.Expr_CallExpr#`,
},
{
I: `1 + 2 * 3 - 1 / 2 == 6 % 1`,
P: `_==_(
_-_(
_+_(
1^#1:*expr.Constant_Int64Value#,
_*_(
2^#3:*expr.Constant_Int64Value#,
3^#5:*expr.Constant_Int64Value#
)^#4:*expr.Expr_CallExpr#
)^#2:*expr.Expr_CallExpr#,
_/_(
1^#7:*expr.Constant_Int64Value#,
2^#9:*expr.Constant_Int64Value#
)^#8:*expr.Expr_CallExpr#
)^#6:*expr.Expr_CallExpr#,
_%_(
6^#11:*expr.Constant_Int64Value#,
1^#13:*expr.Constant_Int64Value#
)^#12:*expr.Expr_CallExpr#
)^#10:*expr.Expr_CallExpr#`,
},
{
I: `1 + +`,
E: `
ERROR: <input>:1:5: Syntax error: mismatched input '+' expecting {'[', '{', '(', '.', '-', '!', 'true', 'false', 'null', NUM_FLOAT, NUM_INT, NUM_UINT, STRING, BYTES, IDENTIFIER}
| 1 + +
| ....^
ERROR: <input>:1:6: Syntax error: mismatched input '<EOF>' expecting {'[', '{', '(', '.', '-', '!', 'true', 'false', 'null', NUM_FLOAT, NUM_INT, NUM_UINT, STRING, BYTES, IDENTIFIER}
| 1 + +
| .....^
`,
},
{
I: `"abc" + "def"`,
P: `_+_(
"abc"^#1:*expr.Constant_StringValue#,
"def"^#3:*expr.Constant_StringValue#
)^#2:*expr.Expr_CallExpr#`,
},
{
I: `{"a": 1}."a"`,
E: `ERROR: <input>:1:10: Syntax error: mismatched input '"a"' expecting IDENTIFIER
| {"a": 1}."a"
| .........^`,
},
{
I: `"\xC3\XBF"`,
P: `"ÿ"^#1:*expr.Constant_StringValue#`,
},
{
I: `"\303\277"`,
P: `"ÿ"^#1:*expr.Constant_StringValue#`,
},
{
I: `"hi\u263A \u263Athere"`,
P: `"hi☺ ☺there"^#1:*expr.Constant_StringValue#`,
},
{
I: `"\U000003A8\?"`,
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#`,
},
{
I: `"\xFh"`,
E: `ERROR: <input>:1:1: Syntax error: token recognition error at: '"\xFh'
| "\xFh"
| ^
ERROR: <input>:1:6: Syntax error: token recognition error at: '"'
| "\xFh"
| .....^
ERROR: <input>:1:7: Syntax error: mismatched input '<EOF>' expecting {'[', '{', '(', '.', '-', '!', 'true', 'false', 'null', NUM_FLOAT, NUM_INT, NUM_UINT, STRING, BYTES, IDENTIFIER}
| "\xFh"
| ......^`,
},
{
I: `"\a\b\f\n\r\t\v\'\"\\\? Illegal escape \>"`,
E: `ERROR: <input>:1:1: Syntax error: token recognition error at: '"\a\b\f\n\r\t\v\'\"\\\? Illegal escape \>'
| "\a\b\f\n\r\t\v\'\"\\\? Illegal escape \>"
| ^
ERROR: <input>:1:42: Syntax error: token recognition error at: '"'
| "\a\b\f\n\r\t\v\'\"\\\? Illegal escape \>"
| .........................................^
ERROR: <input>:1:43: Syntax error: mismatched input '<EOF>' expecting {'[', '{', '(', '.', '-', '!', 'true', 'false', 'null', NUM_FLOAT, NUM_INT, NUM_UINT, STRING, BYTES, IDENTIFIER}
| "\a\b\f\n\r\t\v\'\"\\\? Illegal escape \>"
| ..........................................^`,
},
{
I: `"😁" in ["😁", "😑", "😦"]`,
P: `@in(
"😁"^#1:*expr.Constant_StringValue#,
[
"😁"^#4:*expr.Constant_StringValue#,
"😑"^#5:*expr.Constant_StringValue#,
"😦"^#6:*expr.Constant_StringValue#
]^#3:*expr.Expr_ListExpr#
)^#2:*expr.Expr_CallExpr#`,
},
{
I: ` '😁' in ['😁', '😑', '😦']
&& in.😁`,
E: `ERROR: <input>:2:7: Syntax error: extraneous input 'in' expecting {'[', '{', '(', '.', '-', '!', 'true', 'false', 'null', NUM_FLOAT, NUM_INT, NUM_UINT, STRING, BYTES, IDENTIFIER}
| && in.😁
| ......^
ERROR: <input>:2:10: Syntax error: token recognition error at: '😁'
| && in.😁
| .........^
ERROR: <input>:2:11: Syntax error: missing IDENTIFIER at '<EOF>'
| && in.😁
| ..........^`,
},
{
I: "as",
E: `ERROR: <input>:1:1: reserved identifier: as
| as
| ^`,
},
{
I: "break",
E: `ERROR: <input>:1:1: reserved identifier: break
| break
| ^`,
},
{
I: "const",
E: `ERROR: <input>:1:1: reserved identifier: const
| const
| ^`,
},
{
I: "continue",
E: `ERROR: <input>:1:1: reserved identifier: continue
| continue
| ^`,
},
{
I: "else",
E: `ERROR: <input>:1:1: reserved identifier: else
| else
| ^`,
},
{
I: "for",
E: `ERROR: <input>:1:1: reserved identifier: for
| for
| ^`,
},
{
I: "function",
E: `ERROR: <input>:1:1: reserved identifier: function
| function
| ^`,
},
{
I: "if",
E: `ERROR: <input>:1:1: reserved identifier: if
| if
| ^`,
},
{
I: "import",
E: `ERROR: <input>:1:1: reserved identifier: import
| import
| ^`,
},
{
I: "in",
E: `ERROR: <input>:1:1: Syntax error: mismatched input 'in' expecting {'[', '{', '(', '.', '-', '!', 'true', 'false', 'null', NUM_FLOAT, NUM_INT, NUM_UINT, STRING, BYTES, IDENTIFIER}
| in
| ^
ERROR: <input>:1:3: Syntax error: mismatched input '<EOF>' expecting {'[', '{', '(', '.', '-', '!', 'true', 'false', 'null', NUM_FLOAT, NUM_INT, NUM_UINT, STRING, BYTES, IDENTIFIER}
| in
| ..^`,
},
{
I: "let",
E: `ERROR: <input>:1:1: reserved identifier: let
| let
| ^`,
},
{
I: "loop",
E: `ERROR: <input>:1:1: reserved identifier: loop
| loop
| ^`,
},
{
I: "package",
E: `ERROR: <input>:1:1: reserved identifier: package
| package
| ^`,
},
{
I: "namespace",
E: `ERROR: <input>:1:1: reserved identifier: namespace
| namespace
| ^`,
},
{
I: "return",
E: `ERROR: <input>:1:1: reserved identifier: return
| return
| ^`,
},
{
I: "var",
E: `ERROR: <input>:1:1: reserved identifier: var
| var
| ^`,
},
{
I: "void",
E: `ERROR: <input>:1:1: reserved identifier: void
| void
| ^`,
},
{
I: "while",
E: `ERROR: <input>:1:1: reserved identifier: while
| while
| ^`,
},
{
I: "[1, 2, 3].map(var, var * var)",
E: `ERROR: <input>:1:14: argument is not an identifier
| [1, 2, 3].map(var, var * var)
| .............^
ERROR: <input>:1:15: reserved identifier: var
| [1, 2, 3].map(var, var * var)
| ..............^
ERROR: <input>:1:20: reserved identifier: var
| [1, 2, 3].map(var, var * var)
| ...................^
ERROR: <input>:1:26: reserved identifier: var
| [1, 2, 3].map(var, var * var)
| .........................^`,
},
{
I: "func{{a}}",
E: `ERROR: <input>:1:6: Syntax error: extraneous input '{' expecting {'}', ',', IDENTIFIER}
| func{{a}}
| .....^
ERROR: <input>:1:8: Syntax error: mismatched input '}' expecting ':'
| func{{a}}
| .......^
ERROR: <input>:1:9: Syntax error: extraneous input '}' expecting <EOF>
| func{{a}}
| ........^`,
},
{
I: "msg{:a}",
E: `ERROR: <input>:1:5: Syntax error: extraneous input ':' expecting {'}', ',', IDENTIFIER}
| msg{:a}
| ....^
ERROR: <input>:1:7: Syntax error: mismatched input '}' expecting ':'
| msg{:a}
| ......^`,
},
{
I: "{a}",
E: `ERROR: <input>:1:3: Syntax error: mismatched input '}' expecting ':'
| {a}
| ..^`,
},
{
I: "{:a}",
E: `ERROR: <input>:1:2: Syntax error: extraneous input ':' expecting {'[', '{', '}', '(', '.', ',', '-', '!', 'true', 'false', 'null', NUM_FLOAT, NUM_INT, NUM_UINT, STRING, BYTES, IDENTIFIER}
| {:a}
| .^
ERROR: <input>:1:4: Syntax error: mismatched input '}' expecting ':'
| {:a}
| ...^`,
},
{
I: "ind[a{b}]",
E: `ERROR: <input>:1:8: Syntax error: mismatched input '}' expecting ':'
| ind[a{b}]
| .......^`,
},
{
I: `--`,
E: `
ERROR: <input>:1:3: Syntax error: no viable alternative at input '-'
| --
| ..^
ERROR: <input>:1:3: Syntax error: mismatched input '<EOF>' expecting {'[', '{', '(', '.', '-', '!', 'true', 'false', 'null', NUM_FLOAT, NUM_INT, NUM_UINT, STRING, BYTES, IDENTIFIER}
| --
| ..^`,
},
{
I: `?`,
E: `
ERROR: <input>:1:1: Syntax error: mismatched input '?' expecting {'[', '{', '(', '.', '-', '!', 'true', 'false', 'null', NUM_FLOAT, NUM_INT, NUM_UINT, STRING, BYTES, IDENTIFIER}
| ?
| ^
ERROR: <input>:1:2: Syntax error: mismatched input '<EOF>' expecting {'[', '{', '(', '.', '-', '!', 'true', 'false', 'null', NUM_FLOAT, NUM_INT, NUM_UINT, STRING, BYTES, IDENTIFIER}
| ?
| .^`,
},
{
I: `[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[['too many']]]]]]]]]]]]]]]]]]]]]]]]]]]]
]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]
]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]
]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]`,
E: "ERROR: <input>:-1:0: expression recursion limit exceeded: 250",
},
}
type testInfo struct {
// I contains the input expression to be parsed.
I string
// P contains the type/id adorned debug output of the expression tree.
P string
// E contains the expected error output for a failed parse, or "" if the parse is expected to be successful.
E string
// L contains the expected source adorned debug output of the expression tree.
L string
}
type metadata interface {
GetLocation(exprID int64) (common.Location, bool)
}
type kindAndIDAdorner struct {
}
func (k *kindAndIDAdorner) GetMetadata(elem interface{}) string {
switch elem.(type) {
case *exprpb.Expr:
e := elem.(*exprpb.Expr)
var valType interface{} = e.ExprKind
switch valType.(type) {
case *exprpb.Expr_ConstExpr:
valType = e.GetConstExpr().ConstantKind
}
return fmt.Sprintf("^#%d:%s#", e.Id, reflect.TypeOf(valType))
case *exprpb.Expr_CreateStruct_Entry:
entry := elem.(*exprpb.Expr_CreateStruct_Entry)
return fmt.Sprintf("^#%d:%s#", entry.Id, "*expr.Expr_CreateStruct_Entry")
}
return ""
}
type locationAdorner struct {
sourceInfo *exprpb.SourceInfo
}
var _ metadata = &locationAdorner{}
func (l *locationAdorner) GetLocation(exprID int64) (common.Location, bool) {
if pos, found := l.sourceInfo.Positions[exprID]; found {
var line = 1
for _, lineOffset := range l.sourceInfo.LineOffsets {
if lineOffset > pos {
break
} else {
line++
}
}
var column = pos
if line > 1 {
column = pos - l.sourceInfo.LineOffsets[line-2]
}
return common.NewLocation(line, int(column)), true
}
return common.NoLocation, false
}
func (l *locationAdorner) GetMetadata(elem interface{}) string {
var elemID int64
switch elem.(type) {
case *exprpb.Expr:
elemID = elem.(*exprpb.Expr).Id
case *exprpb.Expr_CreateStruct_Entry:
elemID = elem.(*exprpb.Expr_CreateStruct_Entry).Id
}
location, _ := l.GetLocation(elemID)
return fmt.Sprintf("^#%d[%d,%d]#", elemID, location.Line(), location.Column())
}
func TestParse(t *testing.T) {
p, err := NewParser(Macros(AllMacros...))
if err != nil {
t.Fatal(err)
}
for i, tst := range testCases {
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(tt *testing.T) {
// Runs the tests in parallel to ensure that there are no data races
// due to shared mutable state across tests.
tt.Parallel()
src := common.NewTextSource(tc.I)
expression, errors := p.Parse(src)
if len(errors.GetErrors()) > 0 {
actualErr := errors.ToDisplayString()
if tc.E == "" {
tt.Fatalf("Unexpected errors: %v", actualErr)
} else if !test.Compare(actualErr, tc.E) {
tt.Fatalf(test.DiffMessage("Error mismatch", actualErr, tc.E))
}
return
} else if tc.E != "" {
tt.Fatalf("Expected error not thrown: '%s'", tc.E)
}
actualWithKind := debug.ToAdornedDebugString(expression.Expr, &kindAndIDAdorner{})
if !test.Compare(actualWithKind, tc.P) {
tt.Fatal(test.DiffMessage("structure", actualWithKind, tc.P))
}
if tc.L != "" {
actualWithLocation := debug.ToAdornedDebugString(expression.Expr, &locationAdorner{expression.SourceInfo})
if !test.Compare(actualWithLocation, tc.L) {
tt.Fatal(test.DiffMessage("location", actualWithLocation, tc.L))
}
}
})
}
}
func TestExpressionSizeCodePointLimit(t *testing.T) {
if _, err := NewParser(Macros(AllMacros...), ExpressionSizeCodePointLimit(-2)); err == nil {
t.Fatalf("got %q, want %q", err, "expression size code point limit must be greater than or equal to -1: -2")
}
p, err := NewParser(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))
}
}