1. 有二义性的文法(虽然考虑了运算符的结合性,但却忽略了优先级)
expr -> expr + term
| expr - term | expr * term | expr / term | termterm -> NUMBER
| ( expr )2. 有二义性的文法(虽然考虑了运算符的优先级,但却忽略了结合性)
expr -> term + term
| term - term | termterm -> factor * factor
| factor / factor | factorfactor -> NUMBER
| ( expr )3. 正确的但没有消除左递归的文法(同时考虑到了运算符的优先级和结合性)
expr -> expr + term
| expr - term | termterm -> term * factor
| term / factor | factorfactor -> NUMBER
| ( expr )4. 消除了左递归后的正确文法
消除直接左递归的方法: 假设原文法 A –> Aα1|Aα2...|Aαm|β1|β2|...|βn其中,每个βi都不以A开头。然后用下面的产生式代替A产生式:
A –> β1A'|β2A'|...|βnA'
A' –> α1A'|α2A'|...|αmA'|ε
这样就消除了直接左递归。
expr –> term moreterms
moreterms –> + term moreterms
| – term moreterms
| ε
term –> factor morefactors
morefactors –> * factor morefactors
| / factor morefactors
| ε
factor –> NUMBER
| ( expr )
前面都是使用的BNF表示法描述的文法,下面来看一下用EBNF表示法来描述的计算器程序的文法。
5. 使用EBNF来表述的计算器程序的文法
expr –> term { + term }
| term { – term }
term –> factor { * factor }
| factor { / factor }
factor –> NUMBER
| ( expr )
在这里,花括号表示为0个或多个的意思。比如 expr –> term { + term } 就表示0个或多个"+ term”附加到左边的term上,所以表示运算符的结合性是左结合的。通过这样,运算符的结合性就很自然的表达了出来,而不用通过BNF那样需要显示的表示为 expr –> expr + term 来表示,而且在BNF里面这样的表述是有左递归的,还需要进一步的消除左递归,而消除左递归后的文法已经面目全非,失去了美感…… 最后,EBNF这样的表述的优点还在于翻译代码时可以简化代码,比如: { + term } 可以翻译为循环,而同样的在消除了左递归的BNF表示法当中,此处要多调用一个moreterms函数。