Optional chaining operators allow accessing nested object properties
in a secure manner, without the need to check the entire reference
chain for validity.


1. The `?.` operator can be used to lookup a named property in a
left-hand side expression without having to check whether the lhs
value is a proper object.

-- Expect stdout --
true
true
-- End --

-- Testcase --
{%
	obj = { foo: 1 };

	print(obj.bar?.baz == null, "\n");	// obj.bar is null
	print(obj.foo?.bar == null, "\n");	// obj.foo is not an object
%}
-- End --


2. The `?.[…]` operator complements the `?.` one and applies the
same semantics to computed property accesses.

-- Expect stdout --
true
true
true
true
-- End --

-- Testcase --
{%
	obj = { foo: 1 };
	arr = [ 1, 2 ];

	print(obj["bar"]?.["baz"] == null, "\n");	// obj.bar is null
	print(obj["foo"]?.["bar"] == null, "\n");	// obj.foo is not an object
	print(arr[0]?.["foo"] == null, "\n");		// arr[0] is not an object
	print(foo?.[1] == null, "\n");				// foo is not an array
%}
-- End --


3. The `?.(…)` function call operator yields `null` when the left-hand
side value is not a callable function value.

-- Expect stdout --
true
true
-- End --

-- Testcase --
{%
	foo = 1;

	print(foo?.(1, 2, 3) == null, "\n");	// foo is not a function
	print(bar?.("test") == null, "\n");		// bar is null
%}
-- End --


4. Optional chaining operators cannot be used on the left-hand side of
an assignment or increment/decrement expression.

-- Expect stderr --
Syntax error: Invalid left-hand side expression for assignment
In line 2, byte 13:

 `    obj?.foo = 1;`
  Near here -----^


-- End --

-- Testcase --
{%
	obj?.foo = 1;
%}
-- End --

-- Expect stderr --
Syntax error: Invalid increment/decrement operand
In line 2, byte 7:

 `    obj?.foo++;`
           ^-- Near here


-- End --

-- Testcase --
{%
	obj?.foo++;
%}
-- End --
