Bison shift-reduce conflict - Unable to resolve

本秂侑毒 提交于 2019-11-28 11:39:11

You would want to resolve the conflict in favour of shifting the 'else'. Fortunately, bison has done that for you automatically (but it still lets you know about it.)

Section 5.2 of the Bison manual is about precisely this shift/reduce conflict. As it says there, you can eliminate the warning message if you want to with a %expect declaration.

This specific shift/reduce conflict was a large part of the motivation for the resolution strategy of the original yacc parser-generator, as described in the historic paper on yacc, or in the Dragon book, because it is incredibly annoying to eliminate the conflict from a grammar. So the solution to this question is a nice brain-teaser, but should never be deployed in practice. Using Bison's built-in ambiguity elimination is much more readable and maintainable, and there is no imprecision or shame in doing so.

If I recall correctly, this problem is one of the exercises in the Dragon book. The basic outline of the solution goes like this:

  1. There would not be an issue if the statement in if (expression) statement could not be an if statement. else cannot begin a statement, so if ( 0 ) break; cannot be reduced with else in the lookahead. The problem is if (0) if (0) break; else Now, it's not obvious whether else should be shifted (and thereby attached to the second if) or if the second if should be reduced, leaving the else to be shifted onto the first if. Normal practice (and yacc's ambiguity resolution algorithm) dictate the first.

  2. So let's distinguish between complete if-statements and incomplete if-statements. Now we can say that an incomplete if-statement (one without an else clause) cannot be immediately followed by else. In other words, a complete if-statement cannot have an incomplete if-statement as its first enclosed statement.

So we can try something like:

conditional            : complete_conditional
                       | incomplete_conditional
                       ;

complete_conditional   : IF ( expression ) statement_other_than_conditional ELSE statement
                       | IF ( expression ) complete_conditional ELSE statement
                       ;

incomplete_conditional : IF ( expression ) statement
                       ;

And now we need:

statement              : statement_other_than_conditional
                       | incomplete_conditional
                       | complete_conditional
                       ;
akim

See my answer here: Reforming the grammar to remove shift reduce conflict in if-then-else. In my experience, you should never leave "known conflicts", solve them. Using %expect N with N != 0 is not safe, IMHO (GLR aside, of course).

I have tried @rici 's answer (the accepted answer), and it fails.

statement: conditional | statement_other_than_conditional;
conditional: complete_conditional | incomplete_conditional;
complete_conditional: L_IF '(' expression ')' statement_other_than_conditional L_ELSE statement
| L_IF '(' expression ')' complete_conditional L_ELSE statement;
incomplete_conditional: L_IF '(' expression ')' statement;
statement_other_than_conditional: ';';
expression: IDENTIFIER;

$ bison --report=all rrr.y
rrr.y: warning: 2 shift/reduce conflicts [-Wconflicts-sr]

State 14 conflicts: 1 shift/reduce
State 15 conflicts: 1 shift/reduce

State 14

3 conditional: complete_conditional .  [$end, L_ELSE]
6 complete_conditional: L_IF '(' expression ')' complete_conditional . L_ELSE statement

L_ELSE  shift, and go to state 16

L_ELSE    [reduce using rule 3 (conditional)]
$default  reduce using rule 3 (conditional)


State 15

2 statement: statement_other_than_conditional .  [$end, L_ELSE]
5 complete_conditional: L_IF '(' expression ')' statement_other_than_conditional . L_ELSE statement

L_ELSE  shift, and go to state 17

L_ELSE    [reduce using rule 2 (statement)]
$default  reduce using rule 2 (statement)

Chris Dodd's answer is (at least seems to be) good. A bit adapted:

statement: if_statement | noif_statement;

if_statement:
  IF '(' expression ')' statement
| IF '(' expression ')' noif_statement ELSE if_statement
;

noif_statement:
  IF '(' expression ')' noif_statement ELSE noif_statement
| RETURN ';'
;

expression: IDENTIFIER;

If you have further statement rules: if it is not right recursive (does not end with statement) then add just to noif_statement (like RETURN ';'). Else, for example

statement: blah '(' blah ')' statement ;

Duplicate it:

| blah '(' blah ')' if_statement
| blah '(' blah ')' noif_statement

And add the first variant to the if_statements and the second to the noif_statements.

Another strategy that works (the accepted answer by @rici DOES NOT work, the answer @RaqaouPi credits to Chris Dodd does) is to treat code as a series of if-else/for/while prefixes followed, maybe, by a single dangling if or a simple statement at the end. This example is adapted from this Nearley grammar for a C-like language.

Statement         -> StatementPrefix:* (StatementEnd | DanglingIf)
StatementNoDangle -> StatementPrefix:* StatementEnd

StatementPrefix -> "if" _ "(" _ Expression _ ")" _ StatementNoDangle _ "else" _
                 | "while" _ "(" _ Expression _ ")" _

DanglingIf     -> "if" _ "(" _ Expression _ ")" _ Statement
StatementEnd   -> Simple _ ";"
                | "return" (_ Expression):? _ ";"
                | BlockStatement
                | "break" _ ";"
                | "continue" _ ";"
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!