I am trying to write a LBNF/BNFC grammar for a C-like language. In C there are many possible modifiers that you may or may not write in front of a declaration (like inline
, const
, volatile
and so on).
I am trying to write my grammar to reuse code and make the resulting Haskell AST easy to consume. A grammar for types could look like this:
rule TypeName ::= "bool" | "int" | "double" | "void" | Id ;
Type. Type ::= TypeQualifier TypeName;
ConstModifier. TypeModifier ::= "const" ;
VolatileModifier. TypeModifier ::= "volatile" ;
NoModifier. TypeModifier ::= ;
And for a function declaration it might look like this:
Fun. Fun ::= FunModifier Type Id "(" [Param] ")" ";" ;
InlineModifier. FunModifier ::= "inline" ;
NoFunModifier. FunModifier ::= ;
The problem with this is I'm getting a ton of shift/reduce and sometimes even reduce/reduce conflicts due to these optional prefixes. An alternative grammar that avoids these conflicts could look like this:
NotInlinedFun. Fun ::= Type Id "(" [Param] ")" ";" ;
InlinedFun. Fun ::= "inline" Type Id "(" [Param] ")" ";" ;
or
NotInlinedFun. Fun ::= FunRest
InlinedFun. Fun ::= "inline" FunRest;
FunRest. FunRest ::= Type Id "(" [Param] ")" ";" ;
which leads to a Haskell AST like this:
data Fun = AFun FunRest | BFun FunRest | CFun FunRest
data FunRest = FunRest Type Id [Param]
instead of the more appealing
data Fun = Fun Modifier Type Id [Param]
data Modifier = A | B | C
You can see how this could quickly lead to a combinatorial explosion of rules or a Haskell AST that won't be pleasant to use.
How can I best avoid these conflicts?