C# 语法分析器(二)LR(0) 语法分析
系列导航
- (一)语法分析介绍
- (二)LR(0) 语法分析
- (三)LALR 语法分析
- (四)二义性文法
- (五)错误恢复
- (六)构造语法分析器
首先,需要介绍下 LALR 语法分析的基础:LR(0) 语法分析。
还是以之前的算式文法为例:
先来看一下
栈 | 输入 | 动作 |
---|---|---|
移入 | ||
移入 | ||
按照 |
||
按照 |
||
按照 |
||
移入 | ||
移入 | ||
按照 |
||
按照 |
||
按照 |
||
移入 | ||
按照 |
||
按照 |
||
按照 |
||
接受 |
可以看到,LR(0) 语法分析会不断将输入的符号移入到栈中,如果栈里的符号是某个产生式的右部,就会弹出栈内符号并归约为其头部,再将头部符号入栈,直到找到起始非终结符,接受并完成语法分析。
每次都去比较栈里的符号和所有产生式,也可以完成语法分析,但显然这样太过低效,实际使用中会构造出 LR(0) 自动机,利用 LR 语法分析表来提高匹配效率。
一、项和 LR(0) 自动机
LR(0) 语法分析器会通过维护一些状态,来表明我们在语法分析过程中所处的位置,从而决定现在需要移入还是归约。
LR(0) 使用“项”(item)来表示现在已经看到了产生式的哪些部分。项是由产生式再加上一个位于它的右部中某处的点组成的。例如产生式
例如,项
LR(0) 语法分析器的状态,就是这样的项的集合(或者称为“项集”),因此可以用于决定现在需要移入还是归约。这些状态的集合(或者称为“项集族”)就可以构造出 LR(0) 自动机,自动机的状态就对应一个项集。
二、构造 LR(0) 自动机
为了构造 LR(0) 自动机,首先定义一个增广文法(augmented grammar),如果
引入新的开始符号的目的是告诉语法分析器何时应该停止语法分析并接受输入符号串,当且仅当使用产生式
上面算式文法对应的增广文法如下所示:
然后,需要两个函数
项集的闭包
如果
构造闭包的方法很简单:
- 首先
只包含 本身 - 如果
在 中,且 是一个产生式,且项 不在 中,那么就将这个项添加到闭包中。不断应用这个规则,直到没有新项可以添加到 中为止。
还是以之前的算式文法为例,其增广文法的项
其计算过程为:
- 根据规则 1,将
加入闭包。 - 根据规则 2,定点右侧包含
,因此将 的产生式的项(定点位于最左端) 和 加入闭包。 - 现在定点右侧包含
,因此将 的产生式的项 和 加入闭包。 - 现在定点右侧包含
,因此将 的产生式的项 和 加入闭包。 - 现在定点右侧没有更多非终结符,过程终止。
该算法的具体实现可以参见这里。
对于闭包,还可以进一步划分为如下两类:
- 内核项:包含初始项
和所有定点不在最左端的项。 - 非内核项:除了初始项
意外所有定点在最左端的项。
在上面的例子中,只有
这样区分的原因,是在生成语法分析器的过程中,只有内核项需要一直保存在内存中,非内核项只需要在使用时临时计算出来即可,可以有效减少不必要的内存占用。
GOTO 函数
接下来就是另一个函数
拿上面
这个闭包中,定点右边会可能出现
如果计算出算式文法的完整项集,那么其自动机如下图所示,其中阴影部分表示闭包:
图 1 算式文法的 LR(0) 自动机,图片来自编译原理
构造 LR(0) 自动机的具体实现可以参见这里。
三、构造 LR 语法分析表
当然,在实际使用中,肯定要将自动机转换为其它易于处理的的数据结构,就是 LR 语法分析表。
LR 语法分析器一般都会包含两个栈:状态栈和符号栈。状态栈就代表了已归约的非终结符,与余下的输入一起表示了如下的最右句型(状态栈右侧为栈顶)。
本来根据状态栈就足够复原出相应的符号了,但在实际使用中,符号一般都会附加一些额外数据,因此需要一个符号栈来维护这些额外数据。
然后,就需要两个表格
- 移入
,其中 是一个状态。表示需要将 移入栈中,同时将 也移入符号栈。 - 归约
,其中 是产生式的索引。表示需要将栈顶的 归约为产生式头 ,弹出栈顶的多个状态和符号( 长度个),再将归约后的状态和符号压入栈中。 - 接受,表示完成了语法分析过程。
- 报错,
表格中一般不会特意写明。表示在输入中发现了一个错误并应当执行某个错误恢复动作,会在后面再来具体讨论。
对于 LR(0) 文法来说,可以如下构造语法分析表,假设已构造 LR(0) 的项集族
- 根据
构造得到状态 ,状态 的 根据以下方法决定:- 如果
在 中,且 ,那么将 设置为“移入 ”。 - 如果
在 中,那么对于任意非终结符 (包含输入结束),将 设置为“归约 ” - 如果
在 中,那么将 设置为“接受”。
- 如果
- 状态
的 根据以下方法决定:设 是一个非终结符,如果 ,那么 。 - 规则 1 和 2 未定义的所有条目都设置为“报错”。
- 语法分析器的初始状态就是根据
所在项集构造得到的状态。
上面算式文法生成的 LR(0) 语法分析表如下所示:
这里使用 si 表示“移入
如果注意检查前面的 LR(0) 自动机和语法分析表,可以发现状态 2 是包含
这里的移入-归约冲突,就是 LR 语法分析中可能遇到的冲突之一,另一个则是归约-归约冲突,这种情况下无法选择使用哪个产生式进行归约。为了解决冲突,最简单的办法就是向前查看更多符号。例如同样是基于 LR(0) 自动机,但利用
如果允许向前查看一个字符,那么在到达状态 2 时,就可以发现在后一个字符是“”时,只能选择移入而不能归约,因为归约后的非终结符是
使用修正后的语法分析表,就可以正确对
状态栈 | 符号栈 | 输入 | 动作 |
---|---|---|---|
0 | 移入到 4 | ||
0 4 | 移入到 5 | ||
0 4 5 | 按照 5 |
||
0 4 3 | 按照 4 |
||
0 4 2 | 按照 2 |
||
0 4 8 | 移入到 6 | ||
0 4 8 6 | 移入到 5 | ||
0 4 8 6 5 | 按照 5 |
||
0 4 8 6 3 | 按照 4 |
||
0 4 8 6 9 | 按照 1 |
||
0 4 8 | 移入到 11 | ||
0 4 8 11 | 按照 6 |
||
0 3 | 按照 4 |
||
0 2 | 按照 2 |
||
0 1 | 接受 |
有了 LR(0) 语法分析作为基础,下一章就会来介绍 LALR 语法分析。
本系列相关代码都可以在这里找到。
作者:CYJB
出处:http://www.cnblogs.com/cyjb/
GitHub:https://github.com/CYJB/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了