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:
22 Jul 2024 11:27:43 EDT
by
Richard Chang
to Fall 2009 CMSC 431 Homepage