SLR,LALR和LR解析器都可以使用完全相同的表驱动机制来实现。
从根本上讲,解析算法将收集下一个输入令牌T,并查询当前状态S(以及相关的前瞻,GOTO和归约表)以决定要执行的操作:
- SHIFT:如果当前表对令牌T进行SHIFT,则将对(S,T)推送到解析堆栈上,则根据GOTO表对当前令牌所说的内容(例如GOTO(T))更改状态),则提取另一个输入令牌T',然后重复该过程
- 减少:每个状态都有0、1或该状态可能发生的许多可能的减少。如果解析器是LR或LALR,则根据前瞻集检查令牌,以获取该状态的所有有效缩减。如果令牌与针对语法规则G = R1 R2 .. Rn的缩减的先行集匹配,则会发生堆栈缩减和移位:调用G的语义操作,堆栈弹出n次(从Rn),对( S,G)被推入堆栈,新状态S'设置为GOTO(G),并且该循环以相同的令牌T重复。如果解析器是SLR解析器,则最多有一个归约规则状态,因此可以盲目执行还原操作,而无需搜索查看适用的还原。这是单反解析器很有必要知道,如果有是是否减少;这很容易判断每个州是否明确记录了与之关联的减少数量,并且实际上L(AL)R版本需要该计数。
- 错误:如果无法进行SHIFT或REDUCE,则声明语法错误。
那么,如果他们都使用相同的机器,那有什么意义呢?
SLR声称的价值在于其实现的简便性。您不必遍历所有可能的减少量检查前瞻集,因为最多只有一个,并且如果该状态没有SHIFT退出,则这是唯一可行的操作。可以将哪种减少量专门附加到状态上,因此SLR解析机制不必寻找它。在实践中,L(AL)R解析器处理大量有用的语言,并且实施起来几乎不需要额外的工作,除了作为学术练习之外,没有人实现SLR。
LALR和LR之间的区别与表生成器有关。LR解析器生成器跟踪来自特定状态的所有可能的缩减及其精确的超前集合。您最终得到的状态是,每个归约都与其从其左上下文中设置的确切提前行相关联。这往往会建立大量状态。如果用于缩减的GOTO表和观察头集兼容并且不冲突,则LALR解析器生成器愿意组合状态。这样产生的状态数量要少得多,但代价是无法区分LR可以区分的某些符号序列。因此,与LALR解析器相比,LR解析器可以解析更大的语言集,但是解析器表却大得多。在实践中,可以找到与目标语言足够接近的LALR语法,因此状态机的大小值得优化。
所以:所有三个使用相同的机器。在您可以忽略一小部分机械的意义上说,单反是“容易的”,但这并不值得麻烦。LR解析了更广泛的语言集,但是状态表往往很大。这使LALR成为实际选择。
综上所述,值得一提的是,GLR解析器可以使用更复杂的机制但使用完全相同的表(包括LALR使用的较小版本)来解析任何上下文无关的语言。这意味着GLR绝对比LR,LALR和SLR更强大。如果您可以编写标准的BNF语法,则GLR会根据它进行语法分析。机制上的差异是,当GOTO表和/或超前集之间存在冲突时,GLR愿意尝试多个解析。(GLR如何有效地做到这一点是个天才[不是我的],但是不适合本SO帖子)。
对我来说,这是一个非常有用的事实。我构建了程序分析器,代码转换器和解析器是必需的,但“毫无趣味”;有趣的工作是您对解析后的结果所做的工作,因此重点是进行后解析工作。使用GLR意味着我可以相对容易地构建有效的语法,与破解语法以使用LALR可用形式相比。在尝试处理非学术语言(例如C ++或Fortran)时,这一点非常重要,因为在这些语言中,您实际上需要成千上万条规则来很好地处理整个语言,并且您不想花一生的时间来尝试破解语法规则,满足LALR(甚至LR)的限制。
举一个著名的例子,C ++被认为很难解析……被从事LALR解析的人所接受。使用GLR机制,可以使用C ++参考手册后面提供的规则直接解析C ++。(我恰好有一个这样的解析器,它不仅处理香草C ++,而且还处理各种供应商方言。只有在实践中才有可能,因为我们使用的是GLR解析器IMHO。)
[2011年11月,EDIT:我们扩展了解析器,以处理所有C ++ 11。GLR使这一操作变得容易得多。编辑2014年8月:现在处理所有C ++ 17。一切都没有破裂或恶化,GLR仍然是猫的叫声。]