Я пытаюсь написать грамматику LBNF/BNFC для C-подобного языка. В C есть много возможных модификаторов, которые вы можете писать или не писать перед объявлением (например, inline
, const
, volatile
и т. д.).
Я пытаюсь написать свою грамматику для повторного использования кода и сделать полученный Haskell AST удобным для использования. Грамматика для типов может выглядеть так:
rule TypeName ::= "bool" | "int" | "double" | "void" | Id ;
Type. Type ::= TypeQualifier TypeName;
ConstModifier. TypeModifier ::= "const" ;
VolatileModifier. TypeModifier ::= "volatile" ;
NoModifier. TypeModifier ::= ;
А для объявления функции это может выглядеть так:
Fun. Fun ::= FunModifier Type Id "(" [Param] ")" ";" ;
InlineModifier. FunModifier ::= "inline" ;
NoFunModifier. FunModifier ::= ;
Проблема в том, что я получаю массу конфликтов сдвига/уменьшения, а иногда даже уменьшения/уменьшения из-за этих необязательных префиксов. Альтернативная грамматика, которая позволяет избежать этих конфликтов, может выглядеть так:
NotInlinedFun. Fun ::= Type Id "(" [Param] ")" ";" ;
InlinedFun. Fun ::= "inline" Type Id "(" [Param] ")" ";" ;
or
NotInlinedFun. Fun ::= FunRest
InlinedFun. Fun ::= "inline" FunRest;
FunRest. FunRest ::= Type Id "(" [Param] ")" ";" ;
что приводит к такому Haskell AST:
data Fun = AFun FunRest | BFun FunRest | CFun FunRest
data FunRest = FunRest Type Id [Param]
вместо более привлекательного
data Fun = Fun Modifier Type Id [Param]
data Modifier = A | B | C
Вы можете видеть, как это может быстро привести к комбинаторному взрыву правил или AST Haskell, который будет неудобен в использовании.
Как лучше всего избежать этих конфликтов?