前言
如果你对这篇文章可感兴趣,可以点击「【访客必读 - 指引页】一文囊括主页内所有高质量博客」,查看完整博客分类与对应链接。
文章目录
语法分析
- 语法分析方法
- 自下而上:算符优先分析法、LR分析法
- 自上而下:递归下降分析法、预测分析程序
一、自上而下分析
1.1 左递归 & 回溯
1.1.1 面临的问题
-
回溯问题
- 分析过程中,当一个非终结符用某一个候选匹配成功时,这种匹配可能是暂时的
- 因此如果在后续匹配中出错的话,不得不“回溯”
-
文法左递归问题
- P → P α P \rightarrow P\alpha P→Pα,匹配过程会无限循环下去
1.1.2 消除左递归
「消除直接左递归」
-
核心思想:左递归变右递归
-
例题
「消除间接左递归」
-
要求
-
核心思想
- 消除回路中的节点
-
算法
-
例题
S -> Qc | c
Q -> Rb | b
R -> Sa | a
顺序 1:R、Q、S
答案:
S -> abcS' | bcS' | cS'
S' -> abcS' | 空字
顺序 2:S、Q、R
答案:
S -> Qc | c
Q -> Rb | b
R -> bcaR' | caR' | aR'
R' -> bcaR' | 空字
1.1.3 消除回溯
-
消除回溯的核心在于能确定非终结符 A 所有候选可能推出的第一个符号,因此引入 FIRST 集
-
提取左公共因子 —— 令非终结符的所有候选首符集两两不相交
-
由于有一些非终结符会推出空字,因此我们需要定义 FOLLOW 集
1.2 LL(1) 文法
1.2.1 LL(1)定义
-
文法定义
- 第一个 L 表示 “从左到右扫描串”
- 第二个 L 表示 “最左推导”
- 第三个 1 表示 “每一步只需向前查看一个符号”
-
LL(1) 分析法
- LL(1) 文法可以对输入串进行有效的无回溯的自上而下分析
- LL(1) 文法可以对输入串进行有效的无回溯的自上而下分析
1.2.2 FIRST
-
定义
-
求解算法
- 核心思想:用 “对有限产生式的反复扫描” 代替 “穷尽所有推导”
-
构造每个文法符号的 FIREST 集
-
构造任何符号串的 FIRST 集
1.2.3 FOLLOW
-
定义
-
构造每个非终结符的 FOLLOW 集
1.2.4 习题练习
-
求解步骤
- 消除左递归
- 提取左公共因子
- 求出每个非终结符的 FIRST 集、FOLLOW 集
- 判定是否符合 LL(1) 文法
-
练习题
根据LL(1)文法要求,可以判定该文法符合LL(1)。
1.3 递归下降分析器
1.3.1 概述
- 总体思想
- 分析程序由一组子程序组成,对每一语法单位(非终结符)构造一个相应的子程序,识别对应的语法单位
- 定义全局过程、变量
- ADVANCE:把输入串指示器 IP 指向下一个输入符号,即读入一个单词符号
- SYM:IP 当前所指的输入符号
- ERROR:出错处理子程序
- 代码实现概述
- 依据 FIRST、FOLLOW 集进行匹配
- 依据 FIRST、FOLLOW 集进行匹配
1.3.2 程序示例
-
文法
-
Procedure F
PROCEDURE F;
IF SYM = 'i' THEN ADVANCE
ELSE
IF SYM = ')' THEN
BEGIN
ADVANCE;
E;
IF SYM=')' THEN ADVANCE
ELSE ERROR
END
ELSE ERROR;
- PROCEDURE E
PROCEDURE E;
BEGIN
T; E';
END:
- PROCEDURE E’
- 此处可以检查 FOLLOW 集,也可以不检查,对程序正确性没有影响
PROCEDURE E';
IF SYM = '+' THEN
BEGIN
ADVANCE;
T; E';
END
- PROCEDURE T
PROCEDURE T;
BEGIN
F; T';
END
- PROCEDURE T’
PROCEDURE T';
IF SYM='*' THEN
BEGIN
ADVANCE;
F; T';
END
1.4 扩充的巴克斯范式
1.4.1 定义
- 举例
用扩充的巴克斯范式来描述语法,直观易懂,便于表示左递归消除和左公共因子提取。
1.4.2 示例
1.5 预测分析程序
1.5.1 预测分析程序组成
- 总控程序
- 分析表
- 分析栈
1.5.2 构造预测分析表
-
分析产生式语句的FIRST集
-
如果非终结符可以推空,则加入 FOLLOW 集
-
练习例子
1.5.3 预测分析示例
1.5.4 二义性文法
并不是所有文法在消除左递归、提取左公共因子后都符合 LL(1) 文法要求,因此我们可以根据具体情况来修改预测分析表。
-
文法举例
-
二义文法对应的预测分析表
此处出现冲突的格子其实代表的是 if…then if…then…else… 语句中,else 匹配的是第一个 if,还是第二个 if 的问题,因此我们可以根据程序的实际要求进行选择,并删除另一个,使该文法满足 LL(1) 的特性。
二、自下而上分析
自下而上分析采用 “移进-归约” 思想。
- 基本思想
- 用一个寄存符号的先进后出栈,把输入符号一个一个地移进到栈里,当栈顶形成某个产生式的候选式时,即把栈顶的这一部分替换成(归约为)该产生式的左部符号。
- 核心方法
- 算符优先分析法
- 按照算符的优先关系和结合性质进行语法分析
- 适合分析表达式
- LR 分析法
- 规范规约:句柄作为可归约串
- 算符优先分析法
2.1 基础概念
2.1.1 短语
-
定义(短语、直接短语)
-
语法树的角度
- 两代以上子树的叶子结点构成的序列为短语
- 子树只有两代,则为直接短语
2.1.2 素短语
-
定义(素短语、最左素短语)
- 属于短语
- 至少有一个终结符
- 除自身外不再包含更小的素短语
-
示例
2.2 算符优先文法
2.2.1 优先关系
2.2.2 算符文法
-
算符文法定义
-
算符优先文法定义
- 引入终结符的优先级
- 引入终结符的优先级
2.2.3 FIRSTVT、LASTVT
-
FIRSTVT
-
LASTVT
-
具体实现方式
- 利用栈不断遍历所有产生式
- 若栈 STACK 不空,就将栈顶项弹出,记此项为 (Q, a)。对于每个形如 P → Q . . . P\rightarrow Q... P→Q... 的产生式,若 F[P, a] 为假,则变其值为真且将 (P, a) 推进 STACK 栈。
- 上述过程一直重复,直至栈 STACK 为空为止。
-
FIRSTVT、LASTVT 具体作用
-
示例
- 求解时还需要考虑 KaTeX parse error: Expected 'EOF', got '#' at position 1: #̲E# 式子
- 求解时还需要考虑 KaTeX parse error: Expected 'EOF', got '#' at position 1: #̲E# 式子
2.2.4 最左素短语定理
- 具体定理
- 出现了一个山峰式的句型
- 出现了一个山峰式的句型
2.2.5 算符优先分析算法
-
具体算法过程
-
具体归约过程
- 从左至右,终结符对终结符,非终结符对非终结符,且只要求终结符对应匹配
- 从左至右,终结符对终结符,非终结符对非终结符,且只要求终结符对应匹配
-
算符分析树 v.s. 语法树
- 二者不同,分析树可能会更简便
- 二者不同,分析树可能会更简便
2.3 LR 分析法
2.3.1 LR 文法
- LR 文法定义
- 对于一个文法,如果能够构造一张分析表,使得它的每个入口均是唯一确定的,则这个文法就称为 LR 文法。
- LR(k) 文法定义
- 一个文法,如果能用一个每步顶多向前检查 k 个输入符号的 LR 分析器进行分析,则这个文法就称为 LR(k) 文法。
- LR 文法与二义文法
- LR 文法不是二义的,二义文法肯定不会是 LR 的
- LR 文法是二义文法的真子集,因此是二义文法的充分条件
- 实际应用
- 虽然 LR 文法不能涵盖所有二义文法,但程序设计语言仍然使用 LR 分析法,如果分析表中有冲突,则人工手动去除
2.3.2 概念说明
-
LR 分析法
- 关键在于生成分析表,再根据分析表进行语法分析
- 关键在于生成分析表,再根据分析表进行语法分析
-
句柄
- 一个句型的最左直接短语就是该句型的句柄
- 一个句型的最左直接短语就是该句型的句柄
-
规范归约
- 规范归约即每次对句柄进行归约
- 规范归约 = 最左归约 = 最右推导的逆过程
- 最右推导 = 规范推导
- 由规范推导推出的句型称为规范句型
2.3.3 LR 分析过程
-
规范规约的关键在于寻找句柄
- 历史:已移入符号栈的内容
- 展望:根据产生式推测未来可能遇到的输入符号
- 现实:当前的输入符号
-
LR 分析表
- ACTION[s, a]: 当状态 s 面临输入符号 a 时,应采取什么动作
- GOTO[s, X]: 状态 s 面对文法符号 X 时,下一状态是什么
- 分析表中 s 表示移进(shift),r 表示归约(reduce)
其中归约的具体步骤如下所示:
- LR分析过程性质
- 在任何时候,分析栈里面的内容和扫描串剩下的内容拼接起来,总是一个规范句型。
- 一旦栈顶出现了当时栈内内容和栈外输入串拼接出来的那个句型的句柄,马上就会归约。
2.3.4 LR 分析示例
-
图 1
-
图 2
-
图 3
2.4 LR(0) 文法
2.4.1 活前缀
-
概念引入原因
- 为了定义在规范规约时,不会出现 D 这种情况,引入活前缀概念
- 为了定义在规范规约时,不会出现 D 这种情况,引入活前缀概念
-
活前缀定义
- 规范句型的一个前缀,且该前缀不含句柄之后的任何符号
- 活前缀后续符号均为入栈,因此为终结符
2.4.2 文法拓广
- 文法拓广目的
- 与构造 DFA 时引入初态、终态节点目的一致
- 使得文法的 DFA 中只有一个初态、一个终态
2.4.3 LR(0) 项目
2.4.4 识别活前缀的 DFA 构造法1
-
总体思想
- 先构造 NFA,再将 NFA 通过构造的方式转换成 DFA
-
NFA 构造方法
-
DFA 构造举例
-
NFA => DFA
- DFA 的项目集为文法的LR(0)项目集规范族
- 族 = 元素是集合的集合
2.4.5 识别活前缀的 DFA 构造法2
-
总体思想
- 通过引入有效项目的概念,直接构造出 DFA
-
有效项目概念
-
LR(0)项目集规范族构造
-
拓广文法
-
构造闭包
-
状态转移
-
2.4.6 LR(0)分析表构造
-
通过 DFA 构造分析表
-
举例
根据分析表进行句子识别的部分与 “2.3.4 LR分析示例” 的内容一致,此处不再赘述。
2.5 SLR(1) 文法
2.5.1 引入原因
- LR(0) 出错原因
- LR(0) 方法不够强,容易出现 “移进-归约” 冲突与 “归约-归约” 冲突
- 即 LR(0) 分析表构造中,出现冲突
2.5.2 SLR(1) 解决冲突
- 引入 SLR(1) 文法进行冲突解决
- 简单说,就是加入对 FOLLOW 集的考虑,减少冲突
- 简单说,就是加入对 FOLLOW 集的考虑,减少冲突
2.5.3 SLR(1) 分析表
- 构造算法
- SLR(1) 在分析表构造时只需将 FOLLOW 集考虑进去
- SLR(1) 在分析表构造时只需将 FOLLOW 集考虑进去
按上述方法构造出的 ACTION 和 GOTO 表如果不含多重入口,则称该文法为 SLR(1) 文法。
- 构造示例
2.6 LR(1) 文法
2.6.1 引入原因
- 根本原因
- FOLLOW 集提供的信息太泛,其中有些字符其实并不会出现
- 因此我们需要更加精确的文法来构造分析表
2.6.2 LR(k) 项目
- 扩展 LR(0) 项目,引入展望串
2.6.3 DFA 构造
-
项目集的闭包
-
项目集的转换函数
2.6.4 LR(1) 分析表
-
构造算法
-
构造示例
2.7 LALR(1) 文法
-
文法性质
- 项目集个数 = SLR(1)
- 通过合并 LR(1) 中的同心集得到,如果 LR(1) 中原本就没有 “移进-归约” 冲突,则 LALR 中也不会有,但可能会出现 “归约-归约” 冲突
- 文法目的:识别能力比 SLR(1) 更强,但项目集个数一致
-
同心集合并
- 同心集定义
- 两个集合忽略展望串时,完全相同
- 同心集合并举例
- I5 与 I9 同心,合并后为 I59
- 同心集定义
I5: A -> d·, a
B -> d·, c
I9: A -> d·, c
B -> d·, a
I59: A -> d·, a/c
B -> d·, a/c
2.8 LR 文法总结
-
文法能力
- LR(0)、SLR(1)、LALR(1)、LR(1) 文法,前者为后者的真子集
- LR(0)、SLR(1)、LALR(1)、LR(1) 文法,前者为后者的真子集
-
LR(1) 文法
- 具有规范的 LR(1) 分析表的文法称为 LR(1) 文法
- 使用 LR(1) 分析表的分析器叫做一个规范的 LR 分析器