我正在阅读"Flex and Bison"一书,以了解解析器生成器的工作原理,并且有示例:
calclist: /* nothing */
| calclist exp EOL { printf("= %dn", $1); }
;
exp: factor
| exp ADD factor { $$ = $1 + $3; }
| exp SUB factor { $$ = $1 - $3; }
;
factor: term
| factor MUL term { $$ = $1 * $3; }
| factor DIV term { $$ = $1 / $3; }
;
term: NUMBER
| ABS term { $$ = $2 >= 0? $2 : - $2; }
;
在书中说,上面的语法通过使用单独的非终端符号具有隐式优先级。但是它是如何工作的呢?假设我们有以下示例:1 + 3 * 2
(空格只是跳过(我们读取第一个令牌1
它将被推送为NUMBER
或term
或factor
堆叠,它将通过语法"冒泡"多长时间?将从哪个语法规则中检查下一个令牌?为什么这种语法乘法的优先级高于加法?
这具有"隐式"优先级(而不是显式(的原因确实正如文本所说,由于分解语法(单独的非终端(。
通过你的1 + 3 * 2
示例,想象自己是进行解析的计算机,按照每个指令"不折不扣"进行操作。 为了找到一个"exp"(表达式(,你必须首先找到一个因子。 (您的其他选择是从找到"exp"开始,但这必须找到一个"因子"。 所以你必须找到一个因素...但要做到这一点,你必须找到一个"术语",因为一个因素要么是一个术语,要么是一个本身以术语开头的因素。 所以现在你必须找到一个术语,要么是NUMBER
,要么是关键字ABS
。 所以你可以"接受"(语法术语(1
,这实际上是一个NUMBER
,并且你在解析的第一部分已经成功了——找到一个术语。 (现在,您将从令牌流中删除1
,留下+
作为下一个令牌。
现在你有一个项,你也有一个因子(根据定义(,但是为了"完成有一个因子的操作",可以说,你需要尝试更长的匹配:一个因子后跟MUL
或DIV
,后跟一些东西。 您的下一个令牌是+
:它不是 MUL,也不是 DIV。 因此,您被迫停止解析因子并返回整个解析链作为因子:1
是一个因子,下一个令牌仍然+
。
有一个exp(根据定义(,但是为了"完成有一个exp的操作",你再次需要尝试更长的匹配:exp后跟ADD
或SUB
,然后是一些东西。 下一个令牌仍在+
,实际上是一个 ADD ...因此,您必须继续执行exp ADD factor { $$ = $1 + $3 };
规则。
此时,您(作为解析器(将整个事情(到目前为止(推送到堆栈上,然后再次开始寻找合适的非终端 - 在这种情况下,另一个"因素"。 所以你现在从一个因素的规则开始。 您必须寻找一个"术语",如果找到一个,请尝试执行包含MUL
或DIV
的较长版本的规则。 当您完成这部分时,您将看到*
令牌确实是 MUL,您将不得不采用更长的规则,使"因子"结果使用规则的factor MUL term { $$ = $1 * $3; }
部分。 这将接受(又名吃/用完(3 * 2
序列,并返回"因子"的值6
,该值允许您完成推送到解析堆栈的规则。
返回到推送状态后,通过添加 1 并接受(吃掉(完整的表达式来完成 "1 + " 的解析。 当然,1 + 6 是 7,因此您的语法返回正确的值。
优先级是 ADD 和 SUB 的操作数是因子的结果,并且只有因子包含 MUL 和 DIV 运算符。 ADD不与MUL竞争,因为MUL封装在一个术语中。
从解析器的角度考虑这个问题:在解析器考虑术语表达式与 ADD 运算符的关系之前,必须对术语表达式进行缩减,并且这种缩减会消除 MUL 运算符。
给定A + X * Y
,X * Y
表达式简化为term
,这是一个不再表示*
运算符的单一语法符号,因此+
运算符没有任何优先问题;现在只是A + term
。
顺便说一下,语法使用非常规的反向术语。
这些是术语:A + B + C
这些是因素: A*B*C
项被添加(">序列项"(,因子被乘以("整数或多项式的因子"(。
这真的来自这本教科书吗?无论如何,请尝试Aho,Sethi,Ullman的Compilers:Principles,Techniques and Tools。(1988年但经典(。