This is my code for if statements that might have elses or elsifs for the scripting language I'm writing a parser for. I'm actually rather proud of how compact I got it:
@_( 'IF LPAREN expr_comp RPAREN THEN NEWLINE statement_set { elsif_statement } ENDIF',
'IF LPAREN expr_comp RPAREN THEN NEWLINE statement_set { elsif_statement } ELSE NEWLINE statement_set ENDIF',
)
def if_statement(self, p):
expressions = []
expressions.append((p.expr_comp, p[6]))
else_statement = None
if (p[8] == 'else'):
else_statement = p[10]
expressions.extend(p.elsif_statement)
return ('IF', expressions, else_statement)
@_('ELSIF LPAREN expr_comp RPAREN THEN NEWLINE statement_set')
def elsif_statement(self, p):
return (p.expr_comp, p.statement_set)
This generates the following rules:
Rule 64 if_statement -> IF LPAREN expr_comp RPAREN THEN NEWLINE statement_set _3_elsif_statement_repeat ELSE NEWLINE statement_set ENDIF
Rule 65 _3_elsif_statement_repeat -> _3_elsif_statement_items
Rule 66 _3_elsif_statement_repeat ->
Rule 67 _3_elsif_statement_items -> _3_elsif_statement_items _3_elsif_statement_item
Rule 68 _3_elsif_statement_items -> _3_elsif_statement_item
Rule 69 _3_elsif_statement_item -> elsif_statement
Rule 70 if_statement -> IF LPAREN expr_comp RPAREN THEN NEWLINE statement_set _4_elsif_statement_repeat ENDIF
Rule 71 _4_elsif_statement_repeat -> _4_elsif_statement_items
Rule 72 _4_elsif_statement_repeat ->
Rule 73 _4_elsif_statement_items -> _4_elsif_statement_items _4_elsif_statement_item
Rule 74 _4_elsif_statement_items -> _4_elsif_statement_item
Rule 75 _4_elsif_statement_item -> elsif_statement
Rule 76 elsif_statement -> ELSIF LPAREN expr_comp RPAREN THEN NEWLINE statement_set
Unfortunately, it also generates the following conflicts:
Conflicts:
reduce/reduce conflict for ELSIF in state 111 resolved using rule _3_elsif_statement_item -> elsif_statement
rejected rule (_4_elsif_statement_item -> elsif_statement) in state 111
reduce using _3_elsif_statement_item -> elsif_statement with lookahead ELSIF
╭╴
│ elsif_statement ♦ ELSIF LPAREN expr_comp RPAREN THEN NEWLINE statement_set
│ ╰_3_elsif_statement_item╯ ╰elsif_statement───────────────────────────────────────╯
│ ╰_3_elsif_statement_items╯ ╰_3_elsif_statement_item───────────────────────────────╯
│ ╰_3_elsif_statement_items─────────────────────────────────────────────────────────╯
╰╴
reduce using _4_elsif_statement_item -> elsif_statement with lookahead ELSIF
╭╴
│ elsif_statement ♦ ELSIF LPAREN expr_comp RPAREN THEN NEWLINE statement_set
│ ╰_4_elsif_statement_item╯ ╰elsif_statement───────────────────────────────────────╯
│ ╰_4_elsif_statement_items╯ ╰_4_elsif_statement_item───────────────────────────────╯
│ ╰_4_elsif_statement_items─────────────────────────────────────────────────────────╯
╰╴
The code works, but I'm trying to have as few conflicts as possible so that actual problems don't get lost in the noise. I suspect that part of the problem is the automation used for an optional list of items, but my brain is not catching exactly how this is happening.
The problem is that you have two different productions which include ´{ elsif_statement }´. The EBNF expander creates two different non-terminals; it does not attempt to notice that they are the same. That creates a conflict, because the parser can't know which of the two identical non-terminals to use without looking ahead to the elsif
or end
, which it obviously can't do.
There are a number of fixes for this. A simple one is to change elsif_statement
, which recognises a single elsif
clause, to instead recognise a sequence of elsif
clauses.