这是一个更易读的语法:
expr: sum
sum : sum add_op term
| term
term: term mul_op factor
| factor
factor: ID
| '(' expr ')'
add_op: '+' | '-'
mul_op: '*' | '/'
这可以使用相同的模式轻松扩展:
expr: bool
bool: bool or_op conj
| conj
conj: conj and_op comp
| comp
/* This one doesn't allow associativity. No a < b < c in this language */
comp: sum comp_op sum
sum : sum add_op term
| term
term: term mul_op factor
| factor
/* Here we'll add an even higher precedence operators */
/* Unlike the other operators, though, this one is right associative */
factor: atom exp_op factor
| atom
atom: ID
| '(' expr ')'
/* I left out the operator definitions. I hope they are obvious. If not,
* let me know and I'll put them back in
*/
我希望那里的模式或多或少很明显。
这些语法在递归下降解析器中不起作用,因为递归下降解析器在左递归中窒息。您已经通过左递归消除算法运行了语法,您也可以对上面的语法执行此操作。但是请注意,消除左递归或多或少会消除左递归和右递归之间的差异,因此在使用递归下降语法识别解析后,您需要根据您对运算符关联性的了解来修复它,因为关联性不再是语法固有的。
对于这些简单的产生式,消除左递归非常简单,只需两步。我们从一些非终端开始:
foo: foo foo_op bar
| bar
然后我们翻转它以使其具有正确的关联性:
foo: bar foo_op foo
| bar
(如果运算符最初是右结合的,如上面的取幂,则不需要此步骤。)
然后我们需要左因子,因为 LL 解析要求非终结符的每个替代项都有一个唯一的前缀:
foo : bar foo'
foo': foo_op foo
| ε
对上面的每个递归产生式(即除expr、comp 和atom 之外的所有递归产生式)都这样做会产生一个看起来像你开始使用的语法,只是有更多的运算符。
顺便强调一下,这里没有神秘的魔法力量在起作用。当语法说,例如:
term: term mul_op factor
| factor
它的意思是term(或乘积,如果您愿意)不能是乘法的右手参数,但它可以是左手参数。这也是说,如果您处于产品有效的地步,那么您实际上并不需要带有乘法运算符的东西。您可以改用factor。但显然你不能使用求和,因为factor 不会使用求和运算符解析表达式。 (它确实会解析括号内的任何内容。但这些都是括号内的内容。)
这就是语法中隐含关联性和优先级的意义。