我正在尝试为类似C的语言编写LBNF / BNFC语法。在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] ")" ";" ;
或
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
您会看到这如何迅速导致规则组合的混乱或Haskell AST的使用不愉快。
如何最好地避免这些冲突?
答案 0 :(得分:1)
当您在int
之前看不到任何内容时,您不知道是没有变量修饰符还是函数修饰符,正是因为您还不知道{{ 1}}是指变量或函数的返回值。因此,如果解析器仅使用单个超前标记,则必须避免强迫其做出决定。
从无中构造出非终结符是一种强制解析器决定要检查哪种无物的形式,因此也必须避免。但这不是唯一的例子。例如,如果您包含int
,您会发现尝试将其归类为变量修饰符或函数修饰符会导致相同的(减少/减少)冲突。
但是在任何情况下,真正的C语法都更加微妙。例如,以下内容是合法的:
static
这也是:
static inline const int* extract(int arg);
因此,声明可以有很多限定符,而不仅仅是零或一。在某些情况下,重复会有所不同:
/* The second const is irrelevant to this discussion. */
volatile const unsigned char* const reg = 0x01A4;
在其他情况下,它不是:
long long very_wide;
尽管这些约束可以表示为与上下文无关的语法,但我从未见过如此。如您所说,指数爆炸是难以控制的。如C标准中所述,实际的C语法不会尝试此壮举。它只允许声明包含任意可能重复的 declaration-specifiers (请参见第6.7节)的顺序,然后强制语义分析区分正确和错误的序列。