2
$\begingroup$

I want the expression a && b = c in my language to mean that the value of c is assigned to b when a is true, and nothing is done otherwise. In other words, the && operator short-circuits, and the expression b = c on the right-hand side is evaluated only when the left-hand side is true.

How can I define and implement this grammar rule in a programming language?

$\endgroup$
8
  • 2
    $\begingroup$ Just give && and = the same level of priority, but right-associative, so that a && b = c is parsed as a && (b = c) and a = b && c is parsed as a = (b && c) $\endgroup$
    – Stef
    Commented Feb 9 at 10:18
  • 6
    $\begingroup$ C's operator precedence says that && binds tighter than =. Thus, your expression is the same as (a&&b)=c, which doesn't work. To fix, simply add ()'s where needed, e.g. a&&(b=c). $\endgroup$
    – Erik Eidt
    Commented Feb 9 at 10:19
  • 3
    $\begingroup$ In short, in C, we parse that as (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$
    – Erik Eidt
    Commented Feb 9 at 16:36
  • 3
    $\begingroup$ The title of this question is incorrect and confusing, because "successfully parse the expression a && b = c in C" means "parse a && 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$ Commented Feb 9 at 19:55
  • 2
    $\begingroup$ I tried to rewrite the question to get at the part of the core idea that would be on topic for this site. Asking about what's allowed in C is a question for Stack Overflow; and asking about whether a C behaviour is caused by "the grammar" or "the compiler" makes no sense because there are many possible compilers. Of course it's the grammar, unless there's an actual bug in the compiler; if every compiler works the same way then surely it isn't a bug. If you don't know, then doing the testing is your responsibility. If you do find a compiler bug, file a bug report instead of asking here. $\endgroup$ Commented Feb 12 at 4:51

3 Answers 3

4
$\begingroup$

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)

$\endgroup$
7
  • $\begingroup$ Interesting! This effectively builds a more tolerant parser; $a && $b = $c is parsed as $a && ($b = $c) because the parser knows that ($a && $b) = $c wouldn't make sense. In C, a && b = c results in an error because it is parsed as (a && b) = c and then recognised as not making sense. And in some languages, PHP's rules would be a nightmare. Consider C++ and its overloading of operators. You wouldn't want the evaluation of a && b = c to depend on whether && returns an lvalue or not. (Now I'm tempted to try to write a C++ program that compiles with a && b = c...) $\endgroup$
    – Stef
    Commented Feb 9 at 12:47
  • $\begingroup$ @Stef Indeed, it relies on having a fairly conservative definition of "lvalue", and not treating = as a generic "operator" token. In PHP, it's just three very explicit rules: the basic variable '=' expr plus T_LIST '(' array_pair_list ')' '=' expr and '[' array_pair_list ']' '=' expr for two styles of array destructuring (list($a, $b) = $array and [$a, $b] = $array). $\endgroup$
    – IMSoP
    Commented Feb 9 at 12:54
  • 1
    $\begingroup$ @Stef I guess if you wanted run-time lvalues, you could trade off by requiring parentheses around them - lvalue = variable | '(' expr ')' Then ($a && $b) = $c would compile, and attempt to create an lvalue at run-time, but $a && $b = $c would still be equivalent to $a && ($b = $c) $\endgroup$
    – IMSoP
    Commented Feb 9 at 13:28
  • $\begingroup$ If you had C's indirection operator, then *p=5 would parse as *(p=5). And what of a[i]=5 or f()->g=5? $\endgroup$
    – Erik Eidt
    Commented Feb 9 at 16:35
  • $\begingroup$ @ErikEidt It would be no problem to include rules for all of those in the definition of "lvalue"; in fact, although PHP calls the rule "variable", it includes the equivalent of all of them - $$p, $a[$], and f()->g would all be reduced as "variable" in the parser. I've edited my answer to make that clear, as well as incorporating my comment above about allowing ( expression ) as an lvalue. $\endgroup$
    – IMSoP
    Commented Feb 9 at 17:16
1
$\begingroup$

PHP supports this:

<?php
$a = true; $a && $b = 1; echo $b;

Output:

1

Try it online!

Unfortunately, the documentation only says it is a special case in a note:

Note: Although = has a lower precedence than most other operators, PHP will still allow expressions similar to the following: if (!$a = foo()), in which case the return value of foo() is put into $a.

But this could be done by using different left and right operator precedence, just like how you usually implement brackets, where it has a high precedence outside but low precedence inside.

If you use a formal grammar, the number of symbols seems to explode:

level5 = identifier | constant | "(" level0 ")"
level4 = level5
level3 = level3 ("*" | "/") level4 | level4
level2 = level2 ("+" | "-") level3 | level3
level1 = level2 | level2_1
level4_1 = level4 "=" level1
level3_1 = level3 ("*" | "/") level4_1 | level4_1
level2_1 = level2 ("+" | "-") level3_1 | level3_1
level0 = level0 ";" level1 | level1

This may conflict with the Python way of assigning multiple variables:

a, b = 1, 2
$\endgroup$
1
  • $\begingroup$ Looking at the PHP parser there's no obvious explicit "special case" to make this assignment work. I don't understand parser theory well enough, but I think it may just be that there is a production expr: variable '=' expr but no expr: expr '=' expr, so $a && $b = 1; can't result in expr(expr(variable '&&' variable) '=' scalar)), it has to be interpreted as expr(variable '&&' expr(variable '=' scalar)) $\endgroup$
    – IMSoP
    Commented Feb 9 at 11:35
0
$\begingroup$

Aggregating from comments (credits goes to @Stef and @ErikEidt).

Q1: The parser doesn't help in this case. There are 3 ways out of it:

  1. add the expresion non-terminal to the primary-expression non-terminal, (and this would yield an ambiguous grammar).

  2. Give logical operators same precedance as assignment operators. This is a breaking change to all C programs (and any program written in one of the C-family language).

  3. Borrow from Perl, use literal and and or for lower precedance.

Q2: that of the grammar.

$\endgroup$
2
  • 4
    $\begingroup$ To be fair it's a bit hard to understand what you're asking exactly. a && b = c is definitely an error in C, but doesn't have to be an error in another language. It's not a "failure" so much as a definition. The parser in a C compiler successfully parses a && b = c as (a && b) = c, which results in an error because (a && b) is an rvalue whereas = requires an lvalue on the left of =. You want a && b = c to be parsed as a && (b = c), which is totally possible, just not C. $\endgroup$
    – Stef
    Commented Feb 9 at 10:43
  • 2
    $\begingroup$ As an example of a different language making different choices: a < b < c is parsed as (a < b) < c in C, but as syntactic sugar for (a < b) && (b < c) in python. $\endgroup$
    – Stef
    Commented Feb 9 at 10:45

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .