编译原理笔记(三)之语法分析
1. 语法分析的若干问题
-
分析树和语法树
-
分析语法结构的基本方法:自上而下分析方法和自下而上分析方法
-
语法分析的双重含义:(定义规则和执行规则)
- 规定句子形成的规则,也被称为语法规则,程序设计语言的大部分语法规则可以用==上下文无关文法(CFG)==来描述;(立法)
- 根据语法规则标识记号流中的语言结构,也被称为语法分析。(执法)
1.1 语法分析器的作用
- 根据词法分析器提供的记号流,为语法正确的输入**构造分析树**(或语法树)
- 检查输入中的语法(可能包括词法)错误,并**调用出错处理器**进行适当处理

1.2 语法错误的处理原则
1.2.1 源程序中可能出现的错误

1.2.2 语法错误处理的目标
- 清楚而准确地报告错误的出现,地点正确,不漏报、不错报也不多报。
- 迅速地从每个错误中恢复过来,以便分析继续进行。
- 对语法正确源程序的分析速度不应降低太多。
1.2.3 语法错误的基本恢复策略
- 紧急方式恢复
发现错误时,分析器每次抛弃一个输入记号,一直向前搜索,直到输入记号属于某个指定的合法文法记号(称为同步记号)集合为止。 - 短语级恢复
采用串替换的方式对剩余输入进行局部纠正。
典型的局部纠正是用分号代替逗号,删除多余分号,或者插入遗漏的分号等; - 出错产生式
用出错产生式捕捉错误。这是语法分析器生成器YACC采用的方式,它基本上可以被认为是一种预置型的短语级恢复方式。 - 全局纠正
对有语法错误的输入序列x,根据文法G构造相近序列y的语法树,使得x变换成y所需的修改、插入、删除次数最少。代价太大。
2. 上下文无关文法
2.1 上下文无关文法的定义与表示
定义:上下文无关文法(CFG)是一个四元组G=(N,T,P,S),其中
(1)N是非终结符的有限集合
(2)T是终结符的有限集合,且N∩T=∅
(3)P是产生式的有限集合,每个产生式形如:A->α。其中A∈N,被称为产生式的左部;α∈(N∪T)*,被称为产生式的右部。若α=ε,则称A->ε为空产生式(也可以记为A->)
(4)S是非终结符,被称为文法的开始符号
- 由产生式集表示CFG
由于每个产生式中具有A∈N且α∈(N∪T)*,所以,对于一个没有错误的CFG,可以这样区分N和T集合:
仅N是可以出现在产生式左边的符号的集合
T是词法分析器返回的记号的集合(某些约定的特殊终结符除外),根据N∩T=∅可以推断出T是所有不出现在产生式左边的符号的集合
如果再约定S是第一个产生式的左部,则文法可以由其产生式集P代替,即不写四元组,而仅给出P。CFG的产生式表示也称为巴克斯范式(BNF)。
注意:规范的BNF中,“->”用“::=”表示。 - 产生式的一般读法
“->”读作“定义为”或者“可导出” - 终结符与非终结符书写上的区分
一般情况- 非终结符:大写英文字母A、B、C表示
- 终结符:小写字母a、b、c表示
- 文法符号序列:小写希腊字符α、β、δ表示,即大小写混合的英文字母串
- 产生式的缩写形式
把左部非终结符相同的产生式合并成一个产生式,所有产生式右部由或符号(|)连接,每一个右部现在被称为该产生式的一个候选项,各候选项具有平等的权利。
例如:
<!--文法G3.2-->
E->E+E
|E*E
|(E)
|-E
|id
2.2 CFG产生语言的基本方法——推导
-
可以通过推导的方法产生CFG所描述的语言。非正式地讲,推导就是从文法的开始符号S开始,反复使用产生式,将产生式的左部的非终结符替换成右部的文法符号序列(展开产生式,用标记=>表示),直到得到一个终结符序列(句子).
-
栗子:
-(id+id)可以由文法G3.2产生
E=>-E=>-(E)=>-(E+E)=>-(id+E)=>-(id+id) -
定义:将产生式A->γ的右部代替文法符号序列αAβ中的A得到αγβ的过程,称为αAβ直接推导出αγβ,记作:αAβ=>αγβ
![在这里插入图片描述]()
定义:
![在这里插入图片描述]()
2.3 推导、分析树与语法树
- 分析树(具体语法树)
![在这里插入图片描述]()
- 语法树(抽象语法树)
![在这里插入图片描述]()
(a)是上述分析树对应的语法树
(b)是条件语句if condition then s1 else s2 的语法树
2.4 二义性与二义性的消除
2.4.1 二义性
(1)一个句型有多于一颗分析树,仅与文法和句型有关,与采用的推导方法无关
(2)造成二义性的原因,是文法中缺少对文法符号优先级和结合性的规定
2.4.2二义性的消除
为文法的符号规定适当的优先级和结合性
- 两种方法
- 改写二义文法为非二义文法
- 对二义施加限制,规定优先级和结合性
1) 改写二义文法为非二义文法
- 栗子:改写二义文法G3.2为非二义文法G3.4
<!--文法G3.4-->
E->E+T|T
T->T*F|F
F->(E)|-F|id
用文法G3.4对id+id*id重新推导
最左推导:
E => E+T => T+T => F+T => id+T => id+T*F
=> id+F*F => id+id*F => id+id*id
最右推导
E => E+T => E+T*F => F+T*id => E+F*id => E+id*id
=> T+id*id => F+id*id => id+id*id

结论:
- 由于新引入的非终结符限制每一步直接推导均有唯一选择,使得同一句子仅有一颗分析树。
- 最终产生的分析树与推导方法无关,而仅与文法的描述有关。
- 引入新的非终结符,使得直接推导的步骤数增加,分析树的高度增高,从而分析效率降低。
- 越接近S的A与a,优先级越低。
- 对具有递归定义性质的A产生式A->αAβ,若a∈β(A在a的左边),则a具有左结合性质;若a∈α(A在a的右边),则a具有右结合性质;例如E->E+T中E在+的左边,则+具有左结合性质;若产生式形如E->T+E,则+具有右结合性质。
2)为文法符号规定优先级和结合性
在二义文法G3.2中,只要分别为+、*和-规定正确的优先级和结合性,就会使得分析任何一个句子时仅能得到一颗分析树。
3. 语言与文法
程序设计语言的结构均可以用文法来描述
(1)文法给出了精确的、易于理解的语言结构的说明
(2)以文法为基础的语言,以便于加入新的或修改、删除旧的语言结构
(3)有些类别的文法,可以自动生成高效的分析器
3.1 正规式与上下文无关文法
CFG:所有的产生式左边只有一个非终结符
- 正规式到CFG的转换
推论:正规式所描述的语言结构均可以用CFG来描述,反之不一定
步骤:- 构造正规式的NFA
- 若0为初态,则A0为开始符号
- 对于move(i,a)=j,引入产生式Ai->aAj
- 对于move(i,ε)=j,引入产生式Ai->Aj
- 若i是终态,则引入产生式Ai->ε
栗子:r=(a|b)*abb的CFG
<!--文法G3.6-->
A0->aA0|bA0|aA1
A1->bA2
A2->bA3
A3->ε

- 为什么用正规式而不用CFG描述程序设计语言的词法
采用正规式:- 词法规则简单,用正规式描述已足够
- 正规式的表示比CFG更直观、简洁,易于理解
- 有限自动机的构造比下推自动机简单,且分析效率高
- 区分词法和语法,为编译器前端的模块划分提供方便
3.2 上下文有关文法
第一个产生式左边有不止一个符号
所有产生式的左部长度都小于等于右部长度
3.3 形式化语言与自动机
定义:若文法G=(N,T,P,S)的每个产生式α->β中,均有α∈(N∪T)*,且至少含有一个非终结符,β∈(N∪T)*,则称G为0型文法。
对0型文法施加以下第i条限制,即可得到i型文法
(1)G的任何产生式α->β(S->ε除外)均满足|α|<=|β|(|x|表示x中文法符号的个数)
(2)G的任何产生式形如A->β,其中A∈N,β∈(N∪T)*。
(3)G的任何产生式形如A->a,或者A->aB(或者A->Ba),其中A,B∈N,a∈T*。
| 文法 | 产生式 | 语言 | 自动机 |
|---|---|---|---|
| 0型(短语)文法 | α->β | 0型语言(短语结构语言,递归可枚举集) | 图灵机 |
| 1型文法(CSG) | 限制1 | 1型语言(CSL) | 线性界线自动机 |
| 2型文法(CFG) | 限制2 | 2型语言(CFL) | 下推自动机 |
| 3型(正规)文法 | 限制3 | 3型语言(正规语言,正规集) | 有限自动机 |
4. 自上而下语法分析
4.1 自上而下分析的一般方法
基本思想:对于任何一个输入序列,从文法的开始符号开始,进行最左推导,直到得到一个合法句子或者发现一个非法结构。在推导过程中试图用一切可能的方法,自上而下、从左到右为输入序列建立分析树。
存在的问题:
- 左因子:有相同前缀,例如A->αβ1|αβ2
- 左递归:陷入死循环而使分析无法进行下去,例如A->Aα
4.2 消除左递归

-
消除文法的直接左递归
对于产生式A->Aα|β,可以用非左递归的A->βA’和A’->αA’|ε取代
![在这里插入图片描述]()
-
消除文法的左递归
例如:S=>Aa=>Sda
![在这里插入图片描述]()
4.3 提取左因子

当一个文法中既有左递归又含左因子时,一般的做法时先消除左递归。
4.4 递归下降分析
- 写程序
- 递归下降分析是直接以程序的方式模拟产生式产生语言的过程。
- 基本思想:为每一个非终结符构造一个子程序,每一个子程序的过程体中按该产生式的候选项分情况展开,遇到终结符匹配,而遇到非终结符就调用相应非终结符的子程序。
限制:不能有公共左因子和左递归 - 称为:递归下降子程序
- 构造过程:
(1)构造文法的状态转换图并且简化
(2)将状态图转化为EBNF表示
(3)从EBNF构造子程序
4.5 预测分析器
预测分析器:又称为非递归预测分析器或者表驱动的预测分析器,数学模型是下推自动机
组成:预测分析表、一个符号栈和一个驱动器
4.5.1 非递归预测分析器的工作模式
- 下推自动机与格局
下推自动机模型
只读头:ip指向当前输入
下推栈:top指向栈顶
有限状态转移控制:
![在这里插入图片描述]()
预测分析器:
下推栈:符号栈,用来存放终结符与非终结符
有限状态转移控制:预测分析表+驱动器
![在这里插入图片描述]()
下推自动机的工作模式:是一种放幻灯的方式,此处的每张“幻灯片”称为一个格局。
格局是一个三元组:(站内容,剩余输入,改变格局的动作)
分析是从某个初始格局开始的,经过一系列的格局变化,最终到达接受格局,表明分析成功;或者到达出错格局,表明发现一个语法错误。
开始格局:全部输入序列
接受格局:剩余输入应该为空 - 预测分析表中的内容与改变格局的动作
四种改变格局的动作:- 匹配终结符:若栈顶和当前输入终结符相等且不是结束标志#,则分析器弹出栈顶符号(pop),输入指针指向下一个终结符(next(ip))。
- 展开终结符:栈顶符号是非终结符X,当前输入时终结符a,驱动器访问分析表M[X,a];若M[X,a]时X产生式的某候选项,则用此候选项取代栈顶的X。
- 报告分析成功:栈顶和当前输入符号均为#,分析成功并结束。
- 报告出错:
栗子:表3.2给出了文法G3.9’的预测分析表。用算法3.4作为驱动器,表3.2作为分析表,分析输入序列id+id*id;的过程如下。



4.5.2 构造预测分析表
-
计算FIRST集合
![在这里插入图片描述]()
-
计算所有非终结符的FOLLOW集合
![在这里插入图片描述]()
栗子:
![在这里插入图片描述]()
4.5.3 LL(1)文法
一个文法G被称为时LL(1)文法,当且仅当为它构造的预测分析表中不含有多重定义的条目时。由此分析表所组成的分析器被称为LL(1)分析器,它所分析的语言被称为LL(1)语言。
- 第一个L表示从左到右扫描输入序列
- 第二个L表示产生最左推导
- 1表示在确定分析器的每一步动作时间向前看一个终结符
5. 自下而上语法分析
5.1 自下而上分析的基本方法
- 规范归约与“剪句柄”
![在这里插入图片描述]()

- 短语:以非终结符为根的子树中所有从左到右排列的叶子
- 直接短语:只有父子关系的树中所有从左到右排列的叶子(树高为2)
- 句柄:最左边父子关系树中所有从左到右排列的叶子(句柄是唯一的)
![在这里插入图片描述]()
- 移进——归约分析器的工作模式
![在这里插入图片描述]()
- 改变格局的动作:
- 移进(shift):把当前输入中的下一个终结符移进栈
- 归约(reduce):句柄在栈顶已形成,用适当产生式左部代替句柄
- 接受(accept):宣告分析成功
- 报错(error):发现语法错误,调用语法恢复例程
5.2 LR分析
5.2.1 LR分析与LR文法
LR分析的核心是LR分析表和驱动器
LR分析表由两部分组成:一部分是动作表,另一部分是转移表
- 栗子:
-
文法:
![在这里插入图片描述]()
-
移进——归约分析表:
![在这里插入图片描述]()
-
解释:
s:表示当前状态
a:表示终结符
A:表示非终结符
action[s,a]:指示当前栈顶状态为s和输入终结符为a时应进行的下一动作
goto[s,A]:指示当前栈顶为s和非终结符A时的下一状态转移 -
分析过程
![在这里插入图片描述]()
![在这里插入图片描述]()
-
5.2.2 构造SLR(1) 分析器
SLR(1)分析器简称SLR分析器,S表示Simple 简单的意思
- 构造SLR分析表的基本思想:首先构造一个可以识别文法G中所有活前缀的DFA,然后根据DFA和简单的向前看信息构造SLR分析表
- 活前缀与LR(0)项目
活前缀:出现在移进——归约分析器栈中的右句型的前缀
LR(0)项目:简称项目,在它右部的某个位置上,右一个点“.”。对于A->ε,它仅有一个项目A->. 。 - 拓广文法与识别活前缀的DFA
![在这里插入图片描述]()
![在这里插入图片描述]()
![在这里插入图片描述]()
![在这里插入图片描述]()
栗子:
![在这里插入图片描述]()
- 识别活前缀
![在这里插入图片描述]()
























浙公网安备 33010602011771号