I am not an expert, so feel free to correct me if I've missed something fundamental here, but looking at the Bison parser for PHP, the way it is resolved there is not related to precedence, but to the productions defined.
Specifically, most binary operators (including &&
) are defined as accepting any expression on the left-hand side; but assignment is defined as accepting a smaller set of syntax on the left-hand side.
In PHP's case, the token allowed on the left is called variable
, but it might more generally be called an lvalue
. The key limitations are:
- You need to distinguish in the grammar between "assignment operators" and other "binary operators". This may make a difference in a language with liberal operator overloading.
- You need to define in the grammar some subset of expressions which may be valid "lvalues". This can still include cases which fail at a later compiler phase, or even at run-time. It can even include
'(' expression ')'
so that (a && b) = c
compiles, but means something different from a && b = c
.
In the case of PHP:
- PHP does not allow operator overloading or custom operators, but does define combined assignment operators such as
+=
; these are handled with similar rules.
- It also has array destructuring (
[$a, $b] = $array
) which is handled as an additional rule for =
only.
- The
variable
rule includes not just $foo
but also other variable-like things, such as $object->property
and SomeClass::$staticProperty
.
- In fact, it also includes things which can't be assigned to, because of how it's reused in other parts of the parser:
foo() = 42;
gives a compilation error, but not a parser error.
- However, crucially, it doesn't include anything that matches
$a && $b
.
Here's an edited excerpt of PHP's Bison rules (without the target code which generates AST nodes):
expr:
variable
| '[' array_pair_list ']' '=' expr
| variable '=' expr
| variable T_PLUS_EQUAL expr
| expr T_BOOLEAN_AND expr
variable:
simple_variable
| static_member
| array_object_dereferenceable T_OBJECT_OPERATOR property_name
| array_object_dereferenceable T_NULLSAFE_OBJECT_OPERATOR property_name
Let's examine a simplified version with just assignment and logical and:
expression: variable | assignment | and_expression
assignment: variable '=' expression
and_expression: expression '&&' expression
Given the input $a && $b = $c
, the stack will proceed as follows
$a
- shift - variable
&&
- reduce, then shift - expression '&&'
$b
- shift - expression '&&' variable
=
- shift (because and_expression '='
does not match any rule) - expression '&&' variable '='
$c
- shift - expression '&&' variable '=' variable
- reduce -
expression '&&' assignment
- reduce -
expression '&&' expression
- reduce -
and_expression
- reduce -
expression
The resulting AST is equivalent to $a && ($b = $c)
&&
and=
the same level of priority, but right-associative, so thata && b = c
is parsed asa && (b = c)
anda = b && c
is parsed asa = (b && c)
$\endgroup$(a&&b)=c
, which is valid (and recognizable) syntax yet later a semantic error b/c assignment requires something assignable. This is not something to be fixed by parsing, since something assignable can be an expression of considerable complexity. $\endgroup$a && b = c
in C" means "parsea && b = c
as meaning(a && b) = c
." If your question is "How can we parse a && b = c as meaning 'if a is true, set b to c' in a C-like language?" then that's what the title should say. $\endgroup$