UMBC CMSC431, Compiler Design Principles, Fall 2009


Mid-Rule Actions and The Dangling Else


The Dangling Else

We all know the dangling else problem. The "standard" C-like syntax for if-else statements is ambiguous: if_stmt : IF EXPR THEN statement ELSE statement | IF EXPR THEN statement ;

If you run this bison file (else1.y) through bison, you get a shift/reduce conflict. If you use the -v option, the else1.output file will show you the problem. The parser does not know whether to shift the ELSE token to the stack or use rule 10 to reduce to if_stmt.

There are two standard fixes to this problem. First, we can require the programmer to always use braces around the statements in the if part and the else part of an if statement — even when there is only one statement:

if_stmt : IF EXPR THEN stmt_block ELSE stmt_block | IF EXPR THEN stmt_block ; See file else2.y for the full grammar.

The other fix is to rewrite the grammar using "matched statements" and "open statements" as described in our textbook:

if_stmt : matched_if_stmt | open_if_stmt ; matched_if_stmt : IF EXPR THEN matched_if_stmt ELSE matched_if_stmt | other_stmt ; open_if_stmt : IF EXPR THEN if_stmt | IF EXPR THEN matched_if_stmt ELSE open_if_stmt ; See file else3.y for the full grammar.

Mid-Rule Actions

A new problem arises if we add mid-rule actions to our grammar:

if_stmt : IF EXPR { /* ... */ } THEN stmt_block ELSE stmt_block { /* ... */ } | IF EXPR {/* ... */ } THEN stmt_block {/* ... */} ; This creates a reduce/reduce conflict. When bison sees a mid-rule action like this: A : B { /* ... */ } C bison converts the rule into two separate rules: A1 : B { /* ... */ } A : A1 C

Thus, the mid-rule actions in the productions for if_stmt creates two new rules:

if_stmt_1 : IF EXPR { /* ... */ } if_stmt_2 : IF EXPR { /* ... */ } One for each form of the if statement. Hence the reduce/reduce conflict. (See file else2a.y for the full grammar and file else2a.output for the parse table.)

In this special case, you are likely to want to have the same action after EXPR anyway, so this problem can be resolved by "factoring out" the if part:

if_part : IF EXPR { /* do something */} ; if_stmt : if_part THEN stmt_block ELSE stmt_block { /* do something else */ } | if_part THEN stmt_block {/* do something */} ; (File else2b.y has the full grammar.)

Even when we rewrote the grammar with "matched" and "open" if statements, mid-action rules cause reduce/reduce conflicts:

matched_if_stmt : IF EXPR { /* ... */ } THEN matched_if_stmt ELSE { /* ... */ } matched_if_stmt { /* ... */ } | other_stmt ; open_if_stmt : IF EXPR { /* ... */ } THEN if_stmt { /* ... */ } | IF EXPR { /* ... */ } THEN matched_if_stmt ELSE { /* ... */ } open_if_stmt { /* ... */ } ; In this case, we get 2 reduce/reduce conflicts. (File else3a.y has the full grammar and else3a.output has the parse table.)

Again, we can factor out to resolve the conflict, since we want to generate the same code in the two mid-rule actions. In this case, we have to factor out twice: once with if_part and once with if_part_then_matched_stmt:

if_part : IF EXPR { /* ... */ } ; if_part_then_matched_stmt : if_part THEN matched_if_stmt { /* ... */ } ; matched_if_stmt : if_part_then_matched_stmt ELSE matched_if_stmt { /* ... */ } | other_stmt ; open_if_stmt : if_part THEN if_stmt { /* ... */ } | if_part_then_matched_stmt ELSE open_if_stmt { /* ... */ } ; (File else3b.y has the full grammar.)


Last Modified: 15 Oct 2009 16:27:34 EDT by Richard Chang
to Fall 2009 CMSC 431 Homepage