浅谈编译原理——语法分析篇
1. 语法分析简介
语法分析程序的地位:

词法分析程序接受字符流(即源程序),输出记号流,作为语法分析程序的输入(按照自左向右的顺序扫描输入的记号序列),语法分析程序会根据语法规则,判断输入的记号流是否合法,并输出分析树。
分析方法包括:自顶向下分析和自底向上分析。
2. 自顶向下分析方法
2.1 递归下降分析
核心思想是:
- 对每一个非终结符构造一个分析函数
- 用前看符号指导产生式规则的选择
由于上下文无关文法(CFG)中所有的产生式左边只有一个非终结符,所以我们在调用产生式规则的函数后,就分为两种情况:
- 遇到终结符,则直接把这个终结符和句子中对应位置的 token 进行比较,判断是否符合即可;符合就继续,不符合就返回(回溯);
- 遇到非终结符,则调用这个非终结符对应的函数(可能会递归的调用,所以叫递归下降文法);
以一个简单的文法为例,分析 aab 是否为该文法的句子:
S –> ABA –> aA | εB –> b | bB- 从起始状态 开始,调用 的函数,得到非终结符 ;
- 调用 的函数,得到 ,由于 为终结符,则取句子中的 token 进行比较,符合,继续;
- 继续递归调用 的函数,得到 ,取句子中的 token 进行比较,不符合,回溯;
- 调用 的另一个函数,得到 ,符合,继续;
- 调用 的函数,由于前面都是终结符 ,但产生式 1 已经到达结尾,所以选择第二个产生式并调用,得到 ,继续递归调用 的函数,后略;
显然,这种方法不好:
- 如果语法存在左递归(形如 的推导),则会导致递归调用无限进行下去,使得分析过程陷入死循环;
- 通过回溯不断试探,效率低;
2.2 递归调用预测分析
从递归下降分析方法的问题来看,优化的点就在于解决回溯,实现一种确定的、不带回溯的方法 —— 递归调用预测分析。
如何克服回溯?能够根据所面临的输入符号准确地指派一个候选式去执行任务。因此通常需要对文法进行改造,以满足自顶向下的分析方法的要求,具体包括:
- 消除文法的二义性
- 消除左递归
- 提取左公共因子
- 非终结符号 的所有候选式的开头终结符号集两两互不相交;
- 即
注:一般情况下,消除左递归和提取左公共因子后的文法无二义性。
: 由 开始推导,可以推导出的所有开头终结符号的集合(得出的句子开头的所有可能终结符的集合)。
求解 First 集合的方法:对于产生式 ,根据开头的 分情况讨论:
- 是终结符,则 ;
- 是非终结符,则
注:针对考虑 产生式的 FIRST 集合,还需要处理:
- 如果 也是产生式,则 ;
- 如果 ,其中 均含有 ,则将 ;
- 同理如果 均含有 ,则 ;
这里的额外处理事实上是有一个专门的术语:NULLABLE 集合,即所有可直接或间接推导出空串的集合:
NULLABLE 集合的构造方法:对于非终结符 ,若满足 ,则 。
上述对 产生式的处理可以简化为:如果 且 ,则 ;
显然,这个过程是一个当任何集合有变化就要检查其他集合的动态更新过程。
对于 FIRST 集合的理解?FIRST 集合是后续用来构造分析表的,这里构建一个个人的理解视角,给定输入的记号流 :
假如推导的第一步是 ,而 FIRST 集合的作用就是用于判断该推导能否满足文法要求,判断方式就是输入的字符 是否在 中,那么这一步推导是可以接受的,推导变为 ,之后同理。
并且,由于前面对于文法的要求,非终结符号 的所有候选式的开头终结符号集两两互不相交,所以有且只有这种选择,避免了回溯。
预测分析程序的转换图构造方法
1. 消除左递归
这里主要以消除直接左递归为主,实际题目中很少出现间接左递归。
消除直接左递归:假如有 ,则引入一个新的非终结符 ,并添加产生式:
消除间接左递归:在消除 后面的 的直接左递归之前,发现有 ,则用所有已经在新文法产生式中的 替换 ,即将产生式 替换为:
直至 最前面为终结符或大于等于 的非终结符。
2. 提取左公因子
如果有产生式形如:,则提取左公因子 ,将产生式替换为:
3. 绘制转换图
对于每一个非终结符号 ,创建一个初态和终态,对于每个产生式 构造一条创建一条从初态到终态的路径,有向边依次为 。
4. 化简状态转换图
总之就是反复代入化简。

5. 预测分析程序的实现
以下图为例:

void procE(void) { procT(); if (char == '+') { forward pointer; procE(); }}简单来说:
- 对于 的有向边一般不做任何处理;
- 遇到终结符则移动记号流的指针指向后一个待读如的字符;
- 遇到非终结符则调用它的对应函数;
2.3 非递归预测分析/LL(1) 分析算法
分析过程
上面的方法中,虽然不存在回溯和死循环,但函数的调用还是存在嵌套的,所以引入非递归预测分析方法,它通过分析表和分析栈联合控制,消除递归。
按照网上的说法,似乎基本鲜有关于递归调用预测分析的介绍,而是直接介绍 LL(1) 分析算法,对应我们教材上的非递归预测分析。

按照个人理解,分析表是用来解决回溯的,而符号栈则是用来解决函数的递归调用的。
输入:符号串 (词法分析输出的记号流);文法的预测分析表 (手动构造,方法见下/通过语法分析器自动生成);
初始化:
- 将符号串 放入输入缓冲区,注意后面要添加一个
$; - 将
$放入符号栈,再将文法的起始符号 入栈,此时栈顶元素 ; - pointer 指向输入缓冲区的第一个符号,代表当前输入符号 ;
执行分析:
根据符号栈栈顶元素 和输入符号 执行如下循环过程:
- 若
$,则分析成功,停止; - 若
$,说明当前栈顶的终结符命中输入,匹配成功,将 弹出,同时 pointer 前移指向下一个输入符号; - 若 ,说明发现错误;
- 若 ,则访问分析表 ,即找到对应的需要被调用的产生式,根据产生式分情况执行:
- 若 ,则将 弹栈,然后按照 的顺序压入栈( 显然是被最后调用的,所以先入栈,放在底下),
- 若 ,则将 弹栈;
- 若 ,即分析表中没有任何内容,出错;
输出:每次命中分析表对应的产生式时,输出产生式,最终结果为依次调用的产生式序列,相当于一个最左推导的过程。
预测分析表的构造
1. 改写文法:消除左递归 + 消除左公因子;(见前面)
2. 构造 FIRST 集合:使用前面提到的方法;
3. 构造 FOLLOW 集合:
FOLLOW 集合的定义:假定 是文法 的开始符号,对于 的任何非终结符号 ,集合 是在所有句型中,紧跟 之后出现的终结符号或 $ 组成的集合,即所有可以合法的站在此非终结符后面的终结符(可以包括结束符 $ ,但不包括 )的集合。
特别地,若有 ,则规定 $ 。
FOLLOW 集合的构造方法:
- 对于起始符号 ,
$; - 若有 ,则 ,注意除去 ;
- 若有 或 ,则 ;
FOLLOW 集合的意义?确定某个非终结符可能出现在产生式右侧的上下文环境。举个例子,当栈顶为 ,读入的符号为 ,但 不在任何 FIRST 集合中,如果有 ,那么 必须是 的后继字符才能保证最终句子是一个符合语法的句子,即此时调用 FOLLOW 集合。
4. 预测分析表的构造:对于每条产生式 :
- 对所有终结符 ,;
- 对 ,则对所有 ,;
最终留空处均为 error。
LL(1) 文法
可以用上述方法解析的文法称之为 LL(1) 文法,即从左 (L) 向右读入一个程序,最左 (L) 推导,采用一个 (1) 前看符号。要求对应的预测分析表不含多重表项,也就是要求:对所有产生式 ,有:
3. 自底向上分析方法
LL(1) 分析法的优点是不需要回溯,构造方法较简单,且分析速度非常快,每读到第一个符号就可以预测出整个产生式来。缺点是对语法的限制太强,能满足此要求的语法相当少,而将一个不满足此要求的语法改写到满足要求也相当不容易。因此, LL(1) 分析法目前已经应用的比较少了。基于此,我们引入现在被广泛使用的自底向上分析方法。
- 对输入串的扫描:自左向右;
- 分析树的构造:自底向上;
- 分析过程:
- 从输入符号串开始分析
- 查找当前句型的“可归约串”
- 使用规则,把它归约成相应的非终结符号
- 重复
以下列文法为例,输入串为 id + id * id:
E → E + T | TT → T * F | FF → (E) | id一种自底向上的过程如下:
| 步骤 | 当前句型 | 可归约串 | 使用的产生式 | 归约结果 |
|---|---|---|---|---|
| 1 | id + id * id | id | F → id | F |
| 2 | F + id * id | F | T → F | T |
| 3 | T + id * id | id | F → id | F |
| 4 | T + F * id | F | T → F | T |
| 5 | T + T * id | id | F → id | F |
| 6 | T + T * F | T * F | T → T * F | T |
| 7 | T + T | T | E → T | E |
| 8 | E + T | E + T | E → E + T | E |
| 9 | E | - | - | 结束 |
从上述例子可见,分析过程的关键在于如何找出“可归约串”。
3.1 “移进-归约”分析方法
“移进-归约”分析过程
- 把输入符号逐个地移进符号栈中;(即“移进”,把下一个输入符号移入栈顶)
- 当栈顶的符号串形成某个产生式的一个候选式(右部)时,在一定条件下, 把该符号串替换(归约)为该产生式的左部符号;(即“归约”)
- 重复步骤 2 直到栈顶符号串不再是“可归约串”为止;
- 重复步骤 1 ~ 3 直到归约出文法起始符号 ;
- 接收:宣布分析成功,停止分析;
- 错误处理:调用错误处理程序进行诊断和恢复;
简单来说,“移进”相当于“往栈里装符号,等待形成句法单位”,“归约”相当于“识别出一个语法成分,用它的非终结符替代”。通过不断循环“读符号 + 识别结构”,最终构建整个语法树。
规范归约
假定 为文法 的一个句子,若右句型序列 满足:
- ,;
- , 是经过把 的句柄替换为相应产生式的左部符号而得到的;
规范归约相当于最右推导的逆过程,每次只归约当前句型中的句柄(handle),也就是恰好能还原出上一步最右推导的右部的那一部分。
3.2 LR 分析方法
LR 分析是移进-归约分析的一种规范化、自动化的实现方法。即从左 (L) 向右扫描输入符号串,为输入符号串构造一个最右 (R) 推导的逆过程,采用 k 个前看符号。
LR 分析法的基本思想是在归约的过程中,一方面记住移入和归约的整个符号串(历史信息),另一方面通过产生式推测未来可能碰到的输入符号(预测信息)。在分析的每一步,只须根据分析栈当前已移进和归约出的全部文法符号,并至多再向前查看 k 个输入符号,就能确定栈顶的符号串是否构成相对于某一产生式的句柄,从而确定当前所应采取的分析动作(是移进还是按某一产生式进行归约等)。
LR 分析程序的模型及工作过程

栈包括:(两者同步变化)
- 状态栈 S;
- 符号栈 X;
分析表包括:
- 动作表 action:用于指导分析器在遇到不同输入符号时应采取的动作;
- : 遇到输入符号 (包括终结符和非终结符)时的动作;
- shift (移进):将当前输入符号 和状态 压入栈;
- reduce by (归约):若 ,则从栈中弹出 项,栈顶变为 ,然后把 和状态 压入栈;
- accept:宣布分析成功,停止分析;
- error:调用出错处理程序,进行错误恢复;
- : 遇到输入符号 (包括终结符和非终结符)时的动作;
- 状态转移表 goto:用于指导分析器在采取归约动作后应转移到的新状态。
- : 经过 的后继状态;
工作过程如下,初始二元式为 (S_0,a_1a_2\cdots a_n\)(S_0S_1\cdots S_m,a_ia_{i+1}\cdots a_n$)$:
- 若 ,则二元式变为 (S_0S_1\cdots S_mS,a_{i+1}\cdots a_n\)$;
- 若 ,则二元式变为 (S_0S_1\cdots S_{m-r}S,a_ia_{i+1}\cdots a_n\)$;
- 若 ,则分析成功;
- 若 ,则调用错误处理程序;
活前缀:一个规范句型的一个前缀,如果不含句柄之后的任何符号,则称它为该句型的一个活前缀。
同样,分析方法的核心在于构造分析表,不同 LR 算法构造方法不同,下文将具体展开介绍。
LR(0) 算法
关键概念
形态/LR(0) 项目: 一个产生式的解析程度,用一个产生式加一个位置黑点 来表示,黑点左边表示已解析的部分,右部表示待解析的部分,一般也称为 LR(0) 项目。
产生式 有 4 种 LR(0) 项目:
- :起始形态,
- ;
- ;
- :已完成形态/可折叠/形态,即归约项目;
- 文法起始符号 的归约项目又称之为接收项目;
注:产生式 只有一个归约项目 。
前三种根据圆点 后的第一个符号为终结符/非终结符又可以分为:
- 移进项目:圆点后第一个符号为终结符号的 LR(0) 项目;
- 待约项目:圆点后第一个符号为非终结符号的 LR(0) 项目;
拓广文法:是对原始文法的一种扩展,通过在原始文法中添加一个新的开始符号 和产生式 ,使得文法开始符号仅出现在一个产生式的左边,从而使分析器只有一个接受状态。(接收项目唯一)
教材上还讲了有效项目的定义,但没什么用,直接略去。
闭包 的定义和构造:设 是文法 的一个 LR(0) 项目集合,则 是从 出发,并使用以下方法构造的项目集合:
- 中的每一个 LR(0) 项目均属于 ;
- 若 ,且有产生式 ,若 ,则将 加入 ;
- 重复步骤 2 直至 不再增大;
注: 称之为 的延伸形态,延伸的方向是单向的。
上述过程就是把集合 里所有形态的所有延伸形态全部添加进这个集合。
状态:设 是文法 的一个 LR(0) 项目集合,状态即为 ,即状态是进行过闭合操作的 LR(0) 项目(形态)集合。
转移函数 go:若 是文法 的一个 LR(0) 项目集, 是一个文法符号,定义
其中,。
为 遇到符号 时的后继形态,
构造文法 G 的 LR(0) 项目集规范族
输入为文法 ,输出为 的 项目集规范族 :
- 构造文法 的拓广文法 ;
- ;
- 对于 中的每一个项目集 和每一个文法符号 ,如果 不为空且不在 中,则将 加入 ;
- 重复步骤 2 直至 不再更新;
简单来说,就是从初始项目集 开始(事实上 就是起始状态),按以上方法,不断的生成新的项目集(状态),直到不再出现新的项目集(状态)。

如上图所示,这个状态转移图和有限状态自动机的运行图很相似,一般称之为识别文法 所有活前缀的 DFA。不过解析过程并不能按照 DFA 运行,上下文无关文法需要下推自动机来进行识别。
LR(0) 项目集中的冲突及解决
可折叠形态:某状态/项目集中,只有一个项目,且该项目为归约项目。
语法分析中,如果遇到不可折叠状态,就会发生冲突,即:
- 一个项目集 中,既包含 ,又存在 ,即既包含不可折叠形态,又包含可折叠形态(归约项目);
- 这会造成“移进-归约”冲突,也就是无法判断出应该 shift,还是应该 reduce;
- 一个项目集中,既包含 ,又包含 ,即包含多条可折叠形态(归约项目);
- 这会造成“归约-归约”冲突,也就是无法判断出应该采用哪条产生式来归约;
解决方法见下,即 SLR(1) 算法。
判断某文法是否属于 LR(0) 文法
一个文法是LR(0)文法,每个状态/项目集中:
- 要么所有 LR(0) 项目都是“移进-待约项目”;(即不能同时含有可折叠和不可折叠形态)
- 要么只含有唯一的归约项目(可折叠形态);
- 起始符号不出现在任何产生式右部(一般通过拓广文法已经解决了);
SLR(1) 算法
LR(0) 冲突的解决
由于 LR(0) 不向前看下一个符号,大大增加了冲突的可能,因此这种冲突通过向前看若干个输入符号或许能够解决。
还是考虑上面的冲突场景,即 。现在,如果下一个要读入的符号为 ,假设此时允许使用 进行归约,那么进行归约后,符号 紧随 之后,即 。
所以,当
- ;
- 终结符号 ;
则上述冲突全部消失,决策如下:
- 当下一个读入符号 时,用 移进 ,即将 入栈;
- 当下一个读入符号 时,用 归约;
- 当下一个读入符号 时,用 归约;
上述思路即为 SLR(1) 分析表构造的形式化描述。SLR(Simple LR) 算法的核心思想是利用“FOLLOW集”来解决冲突。
SLR(1) 分析表的构造算法
输入:拓广文法 ; 输出: 的 SLR 分析表
- 构造 的 LR(0) 项目集规范族 ;
- 对于状态 的分析动作如下:
- 若 ,且 ,则 ;
- 若 ,则对 ,则 ;
- 若 ,则 action[i,\]=accept$,表示分析成功;
- 若 , 为非终结符,则 ;
- 剩余空白项均置为 error;
- 分析程序的初态为包含 的有效项目集/状态;
判断某文法是否属于 SLR(1) 文法
法一(一般用于证明是):若文法 G 的 SLR(1) 分析表中没有多重定义项,则该文法是 SLR(1) 文法。
法二(一般用于证明不是):不一定要全部计算出 SLR 分析表,可以挑选状态中存在“移进-归约”冲突的,计算一下应用 FOLLOW 集合后是否能解决冲突,如果仍存在冲突(即对应的动作表位置存在多重表项),则该文法不是 SLR(1) 文法。
定理:每一个 SLR(1) 文法都是无二义的文法,但并非无二义的文法都是 SLR(1) 文法。
LR(1) 算法
SLR(1) 算法向前查看下一个符号,并利用 FOLLOW 集进行识别,但这样仍然不准确(不能确保一定能归约),仍会导致冲突。
而 LR(1) 算法更加强大,利用了下一个读入的符号的信息(lookahead,展望符),或者也被称为预测先行(预测下一个应该出现的输入符号)。
关键概念
LR(1) 项目:, 即为展望符,表示在当前处理到 时,下一个读入的终结符必须是 才能进行正确归约。
由于项目引入了展望符,后续的一些计算也发生了一些改变,但构造过程基本一致。
后继形态:形态/项目 遇到符号 转移到后继形态 。(这里的区别只是 LR(1) 项目的变化)
转移函数 go:若 是文法 的一个 LR(1) 项目集, 是一个文法符号,定义
其中,。
延伸形态:若一个形态的黑点后是非终结符,形如 ,且有 ,则 是 的延伸形态。
简单记忆的话,延伸形态的产生式左部是黑点后的非终结符 ,展望符是非终结符 之后的符号串 + 原展望符 的 FIRST 集合。
为什么呢?形态 表示,当前符号栈为 ,期待遇到符号 ,再遇到符号串 ,最后遇到 才能进行归约。我们现在准备“识别”非终结符 ,接下来要读入能推导出 的符号串,即对 的每个产生式建立相应的“延伸形态”。
当我们识别完 B 后,会继续识别 ,所以延伸形态的展望符号应该是 ,不过由于 可能包含 ,那么再后面就是 了,所以展望符号即为 的后继终结符集合,即 。
同理闭包的构造方法也进行微调即可:
闭包的定义及构造方式:
- 中的每一个 LR(1) 项目均属于 ;
- 若 ,且有产生式 ,且 ,若 ,则将 加入 ;
- 重复步骤 2 直至 不再增大;
构造文法 G 的 LR(1) 项目集规范族
输入为文法 ,输出为 的 项目集规范族 :
- 构造文法 的拓广文法 ;
- C=\{closure(\{[S^\prime\rightarrow\cdot S,\]})}$;
- 对于 中的每一个项目集 和每一个文法符号 ,如果 不为空且不在 中,则将 加入 ;
- 重复步骤 2 直至 不再更新;
这里和 LR(0) 项目集规范族的构造过程是完全一致的(除了项目的表示形式不同)。同理,识别文法所有活前缀的 DFA 的构造方法也是一致的。
LR(1) 分析表的构造算法
输入:拓广文法 ; 输出: 的 SLR 分析表
- 构造 的 LR(1) 项目集规范族 ;
- 对于状态 的分析动作如下:
- 若 ,且 ,则 ;
- 若 ,且 ,则 ;
- 若 [S^\prime\rightarrow S\cdot,\]\in I_iaction[i,$]=accept$,表示分析成功;
- 若 , 为非终结符,则 ;
- 剩余空白项均置为 error;
- 分析程序的初态为包含 [S^\prime\rightarrow\cdot S,\]$ 的有效项目集/状态;
相较于 SLR(1) 分析表,LR(1) 分析表的状态数量和每个状态中包含的项目数量都是及其恐怖的。不过状态数的增加带来的是分析能力的提升。
LR(1) 文法
不要求。
相比于 LR(0) 文法,LR(1) 文法允许:
- 一个状态/项目集中同时出现可折叠状态和不可折叠状态;
- 只要可折叠形态的展望符不和不可折叠形态中黑点后面的符号冲突,不然遇到该输入,又分不清该移进还是归约了;
- 也可以含有多条可折叠状态(归约状态);
- 这些可折叠状态的展望符也不能冲突;
即要求:
- 起始符号 S 不能位于任何产生式的右边;(一般通过拓广文法来解决)
- 要求每个状态中:
- 不能同时含有 和 ;
- 否则会引起“移进-归约”冲突;
- 不能同时含有 和 ;
- 否则会引起“归约-归约”冲突;
- 不能同时含有 和 ;
解决冲突的一种方法是利用符号的优先级,不过此处略。
LALR(1) 算法
前面已经提到,LR(1) 构造算法复杂且状态、形态数量多。而 LALR(1) 算法对此进行了一定的优化,其基本思想是将一些相似的状态进行 merge。一种 merge 方法是将 LR(1) 项目集规范族中的所有同心状态集合并,合并时,对应项目的搜索符(展望符)也会合并。原先各自接收的有向边,都改为由合并后的项目集接收;原先各自发出的有向边,都改为由合并后的项目集发出。
关键概念
同心集:如果两个 LR(1) 项目集去掉搜索符号(展望符)之后是相同的,则称这两个项目集具有相同的心(core),即这两个项目集是同心集。
项目集的核(kernal):除去初态项目集外,一个项目集的核(kernel)是由该项目集中那些圆点不在最左边的项目组成。
其中,初态项目集的核只有 [S^\prime\rightarrow\cdot S,\]$。
LALR(1) 的项目集规范族
- 先得到 LR(1) 的项目集规范族;
- 合并 LR(1) 项目集规范族中的同心集;(减少分析表状态数)
- 用核代替项目集;(减少项目集的存储空间)
- 转移函数 go 同样进行合并修改;
LALR(1) 项目集中的冲突
合并可能导致“归约-归约”冲突,但不会引入新的“移进-归约”冲突,因为:
如果合并后产生“移进-归约”冲突,例如 和 被合并到一个项目集中,这说明原来 所在的项目集中还含有形如 的项目,而这两者原来存在于一个项目集,即原来已经存在“移进-归约”冲突。
LALR(1) 分析表的构造算法
输入:拓广文法 ; 输出: 的 SLR 分析表
- 构造 的 LR(1) 项目集规范族 ;
- 合并同心集。得到 ;
- 分析程序的初态为包含 [S^\prime\rightarrow\cdot S,\]J_k$;
- 构造 action 子表,对于状态 的分析动作如下:
- 若 ,且 ,则 ;
- 若 ,则 ;
- 若 [S^\prime\rightarrow S\cdot,\]\in J_iaction[i,$]=accept$,表示分析成功;
- 构造 goto 子表:设合并后 ,原来这些 是同心集,那么显然 也是同心集,把后继状态组成的同心集合并后的集合记为 ,那么。
- 若 , 为非终结符,则 ;
- 其实就是前面用文字描述的,原先各自接收的有向边,都改为由合并后的项目集接收;原先各自发出的有向边,都改为由合并后的项目集发出。
- 剩余空白项均置为 error;
基本与 LR(1) 分析表的构造算法一致,只需对同心集和对应的后继状态组成的同心集进行合并。
LALR(1) 文法
- 先判断是否属于 LR(1) 文法。(构造 LR(1) 分析表,采用前面的方法判断是否有冲突)
- 如果不存在冲突,则是 LR(1) 文法,继续判断:
- 如果不存在同心集,即不用合并,那么显然也还是 LALR(1) 文法;
- 如果存在,则先进行合并,再检查合并后是否有冲突(即判断是否有“归约-归约”冲突);
- 无冲突,是 LALR(1) 文法;
- 有冲突,不是 LALR(1) 文法;
LALR(1) vs. LR(1)
- 形式上与 LR(1) 相同;
- 大小上与 SLR(1)/LR(0) 相当;
- 分析能力介于 SLR(1) 与 LR(1) 之间;
3.3 LR 分析的错误处理与恢复
略。

4. 总结

“二义性文法”表示那些无论如何都无法消除二义性的文法,它们不可能被任何确定性分析方法识别。我们在上面介绍的均为确定性分析方法,即左侧这些。
左侧的分析方法又可以分为左侧(自顶向下)和右侧(自底向上),其中越外层表示的能处理的文法范围越大,分析能力越强,越内层针对文法的限制越多。
TMD,很久没碰上这么一坨大的了💩。
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!



