You only need to parse it differently if it would be "in the cracks" between method accesses and field accesses. If you don't have that distinction you can parse them the same way.
In a language like Python where property access and method access are the same, simply allowing properties to start with a number during the parsing stage would be enough.
On the other hand, in a language where the difference between properties and methods needs to be known at parse time you need a more complex grammar. You would typically have a separate parser rule for all 4 cases. Something like:
method_call := expression "." identifier "(" argument_spec ")"
attribute_access := expression "." identifier
tuple_attribute_access := expression "." number
function_call := "(" expression ")" "(" argument_spec ")" | identifier "(" argument_spec ")"
There is no real possible confusion between method calls and attribute access. But the confusion between function calls combined with function calls and method calls requires breaking the syntax up in many different blocks, and thus will require special casing the tuple access.
This is why Rust allows tuple.0()
but not struct.attribute()
.
If you don't have methods, disambiguate method vs property accesses later than parsing, or use a different syntax for methods than properties, you may not need to special case tuple access.
foo.0()
potentially $\endgroup$foo.bar
, no? $\endgroup$foo.bar
can't be called with()
, so a method callfoo.bar()
is an irreducible expression likeMethodCall(Ident("foo"), "bar", [])
; and there are languages like Python wherefoo.bar()
isCall(Attr(Ident("foo"), "bar"), [])
, i.e. calling the result of the property accessfoo.bar
. Then there are languages like Javascript wherefoo.bar()
could be parsed like the latter but isn't because it's semantically different from(foo.bar)()
. I think the ambiguity only occurs in syntaxes like Javascript's. $\endgroup$x.f
) or a PrimaryExpression, which includes identifiers and parenthesised expressions. It also says the MemberExpression is evaluated first. $\endgroup$let m = foo.bar; m();
or as an expression using the comma operator,(0, foo.bar)()
. The difference is between aCallMemberExpression
and aCallExpression
, which are two different parse rules even though aCallMemberExpression
could be represented instead as aCallExpression
with aMemberExpression
as its first child. The semantic difference is in the binding ofthis
when the function body executes. $\endgroup$