diff --git a/expr_test.go b/expr_test.go index 1c01b74bb..52aff7fd1 100644 --- a/expr_test.go +++ b/expr_test.go @@ -1184,6 +1184,18 @@ func TestExpr(t *testing.T) { `"hello"[1:3]`, "el", }, + { + `[1, 2, 3]?.[0]`, + 1, + }, + { + `[[1, 2], 3, 4]?.[0]?.[1]`, + 2, + }, + { + `[nil, 3, 4]?.[0]?.[1]`, + nil, + }, } for _, tt := range tests { @@ -1281,6 +1293,16 @@ func TestExpr_optional_chaining_nested_chains(t *testing.T) { assert.Equal(t, "baz", got) } +func TestExpr_optional_chaining_array(t *testing.T) { + env := map[string]any{} + program, err := expr.Compile("foo?.[1]?.[2]?.[3]", expr.Env(env), expr.AllowUndefinedVariables()) + require.NoError(t, err) + + got, err := expr.Run(program, env) + require.NoError(t, err) + assert.Equal(t, nil, got) +} + func TestExpr_eval_with_env(t *testing.T) { _, err := expr.Eval("true", expr.Env(map[string]any{})) assert.Error(t, err) diff --git a/parser/parser.go b/parser/parser.go index 54ba07a59..bc620ac68 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -575,10 +575,16 @@ end: func (p *parser) parsePostfixExpression(node Node) Node { postfixToken := p.current for (postfixToken.Is(Operator) || postfixToken.Is(Bracket)) && p.err == nil { + optional := postfixToken.Value == "?." + parseToken: if postfixToken.Value == "." || postfixToken.Value == "?." { p.next() propertyToken := p.current + if optional && propertyToken.Is(Bracket, "[") { + postfixToken = propertyToken + goto parseToken + } p.next() if propertyToken.Kind != Identifier && @@ -662,8 +668,12 @@ func (p *parser) parsePostfixExpression(node Node) Node { node = &MemberNode{ Node: node, Property: from, + Optional: optional, } node.SetLocation(postfixToken.Location) + if optional { + node = &ChainNode{Node: node} + } p.expect(Bracket, "]") } } diff --git a/parser/parser_test.go b/parser/parser_test.go index a6062eae1..b633bd52e 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -747,6 +747,19 @@ func TestParse_optional_chaining(t *testing.T) { }, }, }, + { + "foo.bar?.[0]", + &ChainNode{ + Node: &MemberNode{ + Node: &MemberNode{ + Node: &IdentifierNode{Value: "foo"}, + Property: &StringNode{Value: "bar"}, + }, + Property: &IntegerNode{Value: 0}, + Optional: true, + }, + }, + }, } for _, test := range parseTests { actual, err := parser.Parse(test.input)