目录
总览
第一章 引论
编译和解释的区别
区别在于是否生成目标程序
编译是将高级语言程序翻译为汇编语言程序或二进制语言程序(目标程序),然后由目标程序在计算机上运行得到结果 只翻译不执行 最后得到的是目标程序
解释是接受某个语言的程序进行解释,每解释一句计算机执行一句 边翻译边执行 最后得到的是程序的运行结果
编译程序产生目标程序的执行速度比解释程序的执行速度要快
第一章相关知识点的补充
编译程序是一种翻译程序,将源语言翻译为目标语言
源语言通常为高级语言:C, C++, C#, Java, Lisp, Prolog, Python…
目标语言通常为机器级语言或较低级的虚拟机语言: 汇编语言(Assembly Languages) 机器语言(Machine Languages ) Bytecode(Java 虚拟机语言)
编译过程:包括编译程序的前端、中端和后端
前端(Front End) 实现主要的分析任务,通常以第一次生成中间代码为标志
后端(Back End) 实现主要的综合任务(目标代码生成和优化) 通常以从最后一级中间代码生成目标代码为标志
编译程序的组成按顺序包括词法分析、语法分析、语义分析、中间代码生成、中间代码优化、目标代码生成、优化
表处理和错误处理贯穿整个过程
编译器:完成语义等价程序变换的软件系统
跨平台编译器:编译器分为前端和后端, 前端负责产生与机器无关的中间代码 后端负责将中间代码生成机器指令 这样跨平台编译器只需要一个前端,多个后端。
PL/0语言编译系统由编译程序和解释程序两部分组成,分别为PL/0编译程序和类P-code解释程序
PL/0编译程序是将PL/0源程序翻译成类P-code目标程序,源语言为PL/0,目标语言为类P-code
将编译程序分成若干遍,是为了使程序的结构更加清晰,而非提高效率
词法分析获得词元序列 语法分析获得语法分析树
描述程序设计语言中单词的工具包括正则文法、正则表达式、自动机
为了使编译程序能对程序设计语言进行正确的翻译,必须采用形式化方法定义程序设计语言
第二章 文法和语言
文法类型
设文法G=(Vn,Vt,P,S)
闭包介绍
0型文法(短语文法)
定义:每个产生式左部限制为非终结符和终结符并集的正闭包并且至少包含一个非终结符,右部没有限制,只需要所属非终结符和终结符并集的闭包即可
特点:任何0型语言都是递归可枚举的,反之,递归可枚举集必定是0型语言
1型文法(上下文有关文法)
定义一:每个产生式均满足右部长度大于等于左部长度,除了S -> ε
定义二(更能体现上下文有关):若0型文法中所有产生式具有形式α1Aα2→α1βα2,其中α1,α2∈V*(非终结符和终结符并集的闭包)β∈V+(非终结符和终结符并集的正闭包)A∈Vn
β的长度一定大于等于A 可由个自所属范围推出
上下文有关文法的具体体现:只有在α1Aα2这种特定格式下才允许用β替换A
2型文法(上下文无关文法)
定义:每个产生式左部限制为一个非终结符,右部所属非终结符和终结符并集的闭包
上下文无关文法的具体体现:结合有关文法比较即可,左部限制长度为1,就无上下文一说
3型文法(正规文法)(右线性文法)
定义:产生式左部依旧为一个非终结符,每个产生式形式如A -> αB或A -> B,其中A,B均为非终结符,α所属终结符闭包
额外补充左线性文法:在右线性文法的基础上稍加改动,每个产生式形式如A -> Bα或A -> B即可
短语,直接短语,句柄的应用
句型:可以从识别符号(开始符号)推导出来的符号串
句子:可以从识别符号(开始符号)推导出来的只包含非终结符号的符号串
L(G[S]) 代表句子集合 每个句子都来自文法G[S]
个人理解句柄就是最右推导的最后一步
最左推导:在推导的任何一步α -> β,其中α、β是句型,都是对α中的最左非终结符进行替换
最右推导:在推导的任何一步α -> β,其中α、β是句型,都是对α中的最右非终结符进行替换
最右推导又称为规范推导,由规范推导所得的句型称为规范句型
文法二义性
如果一个文法中存在某个句子它有两个不同的最左推导,则这个文法是二义的
消除二义性可以通过人为规定优先级的方法
第二章相关知识点的补充
构成语言的三要素:符号,语法规则,语义
语言是句子的集合
符号串的运算
连接:符号串x、y的连接,是把y的符号写在x的符号之后得到的符号串xy 如 x=ab,y=cd 则 xy=abcd 有εa = aε=a
方幂:符号串x自身连接n次得到的符号串称为符号串x的方幂,表示为xn 即an = aa…aa(n个a),a0=ε,a1=a, a2=aa 特别的, 设x=ab,则x0=ε, x1= ab, x2=abab, x3=ababab, ……
补充一点 产生式的->代表定义为
L(G[S]) 代表句子集合 每个句子都来自文法G[S]
若两个文法的句子集合一致,则称这两个文法是等价的
注意各型文法对应的识别系统
第二章考题总结
1.给出文法判断二义性,可以采用最左推导画两个结构不一样句子一样的语法树
2.给出文法和文法符号串画语法树,找短语、直接短语、句柄
第三章 词法分析
正规文法和正规式的等价性
正规式转正规文法
重写规则
1.形如A -> xy 的正规产生式 重写为 A -> xB B -> y
2.形如A ->x*y 的正规产生式 重写为 A -> xB A -> y B -> xB B -> y
3.形如A -> x|y 的正规产生式 重写为 A -> x A -> y
例题解析:给出正规产生式 r = a(a|d)* 转换为正规文法
第一步 S -> a(a|d)*
第二步 利用规则1 S -> aA A -> (a|d)*
第三步 利用规则3 A -> a*|d*
第四步 利用规则2 A -> aB | dB A -> ε B -> aB | dB B -> ε
整理最终结果为
S -> aA A -> aB A -> dB A -> ε B -> aB B -> dB B -> ε
正规文法转正规式
转换规则
产生式 正规式
1. A -> xB B -> y A = xy
2. A -> xA | y A = x*y
3. A -> x A -> y A = x | y
例题解析:给出下列正规文法
S -> aA S -> a A -> aA A -> dA A -> a A -> d
根据规则2 A -> (a|d)*(a|d)
根据规则3 S -> aA | a
根据规则1 S -> a(a|d)*(a|d) | a
简单变换可得 S -> a(a|d)*
自动机的3种表示
数学表示(五元组)
M = (有穷集,有穷字母表,转换函数,初态,终态)
举例:M = ({0,1,2,3,4},{a,b},f,{0},{2,4})
{0,1,2,3,4}每个元素对应一个状态 {a,b}每个对应一个输入符号
具体的转换函数:f(0,a) = {0,3} f(1,b) = {2}等等不再列举
状态图表示
矩阵表示
行表示状态、列表示输入符号、矩阵元素代表要转换的新状态、矩阵右侧用01区分终态和非终态
相关集合运算说明
状态集合I的ε-闭包 ε-closure(I): 状态集I中任意状态经任意条ε(包括0条,即原集合状态本身)所到达的状态集合
状态集合I的a弧转换 move(I,a):是所有那些可从I中的某一状态经过一条a弧而到达的状态的集合
状态集合I的a弧转换的ε-闭包: Ia
Ia = 先在集合I的基础上经过一条弧a所到达的状态集合 再在此集合的基础上经任意条ε所到达的状态集合
Ib 同上 把经过的弧改为b
拿第一行的Ia举例:5经a到达5 1经a到达3 在此基础上5经0条ε到达自身 一条ε到达1 3经0条ε到达自身 最终得到{5,3,1}
{X,5,1} -> {5,3} -> {5,3,1}
根据NFA构造DFA(NFA的确定化)
NFA = {状态集合, 输入符号表, 映像, 初态集, 终态集}
DFA = {状态集合, 输入符号表, 映像, 唯一初态, 终态集}
二者的区别:
1.NFA的映射可以是多值映射 比如a--0-->b a--0-->c a可以接收输入符号0到达状态b或c 使状态转移不可预测 而DFA的映射必须是单值映射 不会出现这种问题
2.NFA初态不唯一 DFA初态唯一
3.NFA可以带ε转换 DFA不允许
用双圈表示的是终态结点或含终态元素的结点
第一步 构造状态转移矩阵 第一列从初态的ε闭包开始 其他列分别为每个输入元素对应弧的转移+ε闭包
第二步 对状态转移矩阵重新编号来构造DFA状态转移表 注意此时只要包含终态符号就右侧标1
第三步 根据DFA状态转移表画DFA状态图
关于初态结点和终态结点是同一个节点的理解:
在DFA中初态结点不唯一 即上图中的0不一定只包含一个元素 可能会有多个元素 以初态结点和终态结点的定义来理解 初态即最开始的状态 终态即结束状态 状态转换过程中的最后一个状态
因此在某些DFA中 两态一点的情况可能是对于从这个点的某个初始元素出发 最后回到该点的终态元素
DFA最小化
1.将非终态和终态划分成不同的子集
2.从对于字母表中的每个字符,让每个子集中的每个状态去走一遍,按他们到达的状态属于的子集进行划分,切割成不同的子集,然后对每个新生成的子集重复2,直到不会出现新的子集
3.形成最终不同的子集,重新编号(注意:含有原初态的子集为简化DFA的初态,含有原终态的子集为简化DFA的终态)
若集合中两个状态的后继状态不在一个集合中 则要将这两个状态分开 重复输入符号进行划分直到所有状态都不可再划分 即化简到最小
样例解释:拿最终划分的{1,2}来说 1在接受符号b后状态变为3 2在接受符号b后状态变为3 一致无需再进行划分 对所有子集合都遍历输入符号判断后继状态 一致后得到最终结果
根据正规式构造DFA
首先根据正规式构造NFA,再将NFA转换为DFA,再进行DFA最小化
常用分解变换
第三章相关知识点的补充
词法分析的任务:从左至右逐个字符地对源代码进行扫描,产生一个个词元(Token)
PL/0的词元共5种,32个
保留字(13)begin, call, const, do, end, if, odd, procedure, read, then, var, while, write
标识符(1) ident
数字(1) number
运算符(12)+, -, *, /, odd, =, <>, <, <=, >, >=, :=
分界符(5 ) , ;. ()
有穷自动机(也称有限自动机)作为一种识别装置,能够识别正规文法所生成语言的句子
DFA的状态图只有唯一一个初态节点,可以有多个终态节点
注意初态结点的双箭头和终态结点的双圈,DFA的矩阵表示要在右侧标01区分终态和非终态
DFA定义格式含义(K,Σ,f,S,Z)
K代表有穷集,每个元素代表一种状态
Σ代表一个有穷字母表,每个元素对应一个输入符号
f代表转换函数
S代表初态
Z代表终态集
NFA格式相同 区别在于S代表非空初态集
注意NFA包含有穷字母表、终止状态集合、有限状态集合,不包含初始状态集合,因为初始状态是唯一的
第三章考题总结
1.给出正规式画NFA
2.NFA转DFA(确定化)
3.DFA最小化
第四章 自顶向下语法分析方法
面临的问题:存在多个候选式时如何准确地找到合适的产生式 否则会产生回溯即递归地试探各个候选式
FIRST集
FIRST集的定义:能被非终结符直接推导出的最左边的终结符的集合
用来解决部分候选式选择的问题 以下面为例
假设输入符号S 输入元素为c 由于S的首符号集两两不相交 因此我们可以唯一确定候选式
因此 两两不相交的情况下我们可以根据首符号集来做抉择
当产生式右部最左符号为非终结符时 产生式左部的FIRST集就等于该非终结符的FLRST集
FOLLOW集
FOLLOW集的含义:能紧挨着非终结符后的终结符的集合
两种分析情况
S -> ABT 则FOLLOW(B) = FIRST(T)中的终结符 不包含ε
若FIRST(T)中含ε 代表S -> AB FOLLOW(B) 包含 FOLLOW(S)
关于#属于FOLLOW(S)的解释 #要放到某个句型的最右符号的FOLLOW集 开始符号本身就是一个句型 因此得到FOLLOW(S) = {#}
SELECT集
分析: SLELECT(A->α) 代表当输入元素为什么时 可以选择该产生式 输入元素必须在α可推导句型中的最左边
SELECT(A->β) 同理 特殊的是β可以推导出空 因此要分为两种情况讨论
为空时:A->ε 我们要判断输入元素是否可以选择该产生式时就只能看A的FLOLLOW集
不为空时:A->β 我们要判断输入元素是否在β产生式的最左边 即FIRST(β) 中是否包含输入元素 同时输入元素不为空 因此要去掉FIRST(β)中的ε
LL(1)文法
分析原因:当α和β都不推导出ε时 SELECT(A->α) = FIRST(α) SELECT(A->β) = FIRST(β)
二者无交集就代表每个输入元素都可以唯一确定一个产生式
当有一方可以推导出ε时 SELECT(A->α) = FIRST(α) SELECT(A->β) = (FIRST(β) - {ε}) 和FOLLOW(A)的并集 使能确定语法的条件是FIRST(α) 和 FOLLOW(A) 的交集为空
双方不能同时推导出ε 否则两个SELECT(...)都会包含FOLLOW(A) 会产生交集
以上这些条件是为了保证每个非终结符的各个产生式可选集不相交 每个输入符号可唯一对应一个产生式(补充:应该是当前的输入符号和当前的非终结符可唯一确定一个产生式 一开始非终结符为开始符S)
LL(1)文法的判别算法
以下列文法举例
第一步:筛选出能推导出空的非终结符 包括 S H K M 不能推导出空的非终结符为 L(较明显)
第二步:计算每个文法符号的FIRST集
第一轮
FIRST(S) = {a, ε} + FIRST(M) + FIRST(H)
FIRST(H) = {ε} + FIRST(L)
FIRST(K) = {ε, d}
FIRST(L) = {e}
FIRST(M) = {b} + FIRST(K)
第二轮
FIRST(S) = {a, ε} + FIRST(M) + FIRST(H) = {a, ε, b, d, e }
FIRST(H) = {ε} + FIRST(L) = {ε, e}
FIRST(K) = {ε, d}
FIRST(L) = {e}
FIRST(M) = {b} + FIRST(K) = {b, ε, d}
第三步:计算产生式右部文法符号串的FIRST集
FIRST(MH) = {b, d, e, ε}
FIRST(a) = {a}
FIRST(LSo) = {e}
FIRST(dML) = {d}
FIRST(eHF) = {e}
FIRST(K) = {ε, d}
FIRST(bLM) = {b}
第四步:计算每个非终结符的FOLLOW集 FOLLOW集中应该不存在ε
找产生式右边对应非终结符的右侧的FIRST集 若右侧为空 则找产生式左边的FOLLOW集 并且#要放入开始符的FOLLOW集中,FOLLOW集需要一遍一遍地进行更新,直到不再出现新的更新为止
FOLLOW(S) = {o, #}
FOLLOW(H) = FOLLOW(S) + {f} = {o, #, f}
FOLLOW(K) = FOLLOW(M) = {e, o , #}
对于FOLLOW(L)的解释 由于M可推导出ε 因此要分情况汇总
推出ε 原产生式就等于 M -> bL 即FOLLOW(L) = FOLLOW(M)
不推出ε 原产生式不变 FOLLOW(L) = FIRST(M) - {ε}
对于FIRST(So) S不为空 则为FIRST(S) - {ε} S为空 则为FIRST(o) = {o}
FOLLOW(L) = FIRST(So) + FOLLOW(K) + (FIRST(M) - {ε}) + FOLLOW(M) = {a, b, d, e, o, #}
FOLLOW(M) = FIRST(H) - {ε} + FOLLOW(S) + FIRST(L) = {e, o , #}
第五步:计算每个产生式的SELECT集
若产生式右边可推导出ε 采用FIRST集和FOLLOW集的并集
SELECT(S -> MH) = FIRST(MH) - {ε} + FOLLOW(S) = {b, d, e, o, #}
SELECT(S -> a) = {a}
SELECT(H -> LSo) = FIRST(LSo) = {e}
SELECT(H -> ε) = FOLLOW(H) = {o, #, f} 以下不再一一赘述
判断是否为LL(1)文法取决于同一个非终结符的不同产生式的SELECT集是否有交集 无交集就是
非LL(1)文法的等价转换
文法中存在左公因子或左递归 则该文法一定不是LL(1)文法 可通过提取公因子引入新的非终结符来转换
注意 LL(1)文法不存在左公因子和左递归是个充分条件 不代表不存在左公因子和左递归的文法就是LL(1)文法
提取左公共因子
显性的左公因子
提取公因子 S -> aS(b | ε) 引入新的非终结符A
(1) S -> aSA
(2) S -> ε
(3) A -> b
(4) A -> ε
隐形的左公因子
将(3)带入(2) A -> aAc
更新后的产生式为 A -> ad A -> aAc A -> bBc
B -> aA B -> bB
引入新的非终结符A`
A -> aA` A` -> d | Ac A -> bBc
B -> aA B -> bB
消除左递归
消除直接左递归
具体视频讲解BV1La411Y78T
举例说明:
E -> E + T | T 存在明显的左递归问题
产生式左边为E,将右边不包含E的部分(对应上图中的β)提到右部的前面再加入新的非终结符 E -> TE`(E`对应P`)
E` -> +TE` | ε(+T对应上图的α)
消除间接左递归
可能存在多个产生式合起来存在左递归 一个一个消除替换 直到最后获得一个直接左递归
LL(1)文法的分析过程
分析过程需要一个分析栈(初始包含#E)(E为文法开始符号) 输入的分析串以#结束
程序运行分析的3种动作
1.若栈顶元素 = 当前分析串的输入符号 = # 分析成功
2.若栈顶元素 = 当前分析串的输入符号 != # 栈顶元素弹栈 输入符号指向下一位
3.若栈顶元素 != 当前分析串的输入符号 查看预测分析表
第四章知识点补充
第六章 LR分析
LR(0)分析
对于给定文法 要求构造LR(0)分析表
1.判断开始符号是否存在多个产生式
若是 则进行拓广文法
若构造的活前缀DFA不存在移进-归约冲突和归约-归约冲突 则称该文法为LR(0)文法
上图中每一块都属于一个项目集闭包 在这个闭包中每个项目都是等价的
观察该表中的I2 B->. T->. 二者为归约-归约冲突 将栈顶的空串归约为B还是T
T->. T->.aBd 二者为移进-归约冲突 程序存在是直接进行归约还是接收a后进行移进
2.构造活前缀的DFA(也称为该文法的LR(0)项目集规范族)
补充概念
项目的概念:关于某非终结符号形成句柄的程度,用“.”在产生式右部的位置表示
比如 A->.abc 期望形成关于A的句柄 A->a.bc正在 A->abc.已经形成 可以归约
文法符号栈里符号 + 剩余符号串 = 文法的一个规范句型
文法符号栈里的符号为某一规范句型的前缀
形成句柄之前包括句柄在内的所有前缀称为活前缀
右部长度为n的产生式对应n+1个LR(0)项目
对于A→ε,LR(0)项目只有A→• 且A→ε的LR(0)项目A→ • 是归约项目
LR(0)的项目分类
移进项目,形如 A →a • ab a是终结符
待约项目,形如 A →a• Bb
归约项目,形如 A →a •
接受项目,形如 S’ →S • S’为开始符
3.根据识别句柄的DFA画预测表
根据状态栈的栈顶状态和符号栈的栈顶状态判断要进行的动作
1.如果状态栈是接收状态 比如1 则只有在遇到#时才表示接收成功,其余都是空
2.如果状态栈是归约状态 比如6、7、8、9 则符号栈无论是什么都是执行归约动作rn n对应产生式编号
3.其他情况根据具体DFA自行判断
4.对某符号串进行分析
移进:根据状态栈栈顶元素和输入串当前符号查找分析表 找到对应的Si
将Si入状态栈 当前输入串符号入符号栈 继续进行判断
归约:若对应的是ri i代表对应产生式 在符号栈中用产生式左部替代右部 状态栈也对应去掉相同长 度的状态 之后根据状态栈的栈顶元素和产生式左部查找要替换当前状态栈栈顶的状态
SLR(1)分析
若文法满足LR(0) 一定满足SLR(1) 反之不一定成立
SLR(1)是为了解决如下图所示的移进-归约和归约-归约冲突 (1) S -> rD
具体体现到矩阵中如下图
实现步骤
1.构造活前缀的DFA
2.根据DFA画SLR(1)分析表
SLR(1)分析表和LR(0)分析表的区别在于对存在冲突的状态要进行判断来避免移进-归约冲突和归约-归约冲突
图中存在冲突的项目,其产生式左部都要分析出FOLLOW集 用来判断
注意区分接收项目和归约项目 I1中的第一行是接受项目
FOLLOW(E) = {#,+,)}
分析I2
对于输入符号包含在FOLLOW集中的进行归约,不存在的就根据DFA进行移进
第六章相关知识点补充
LR(0)分析和SLR(1)分析都是自底向上的分析方法
LR(0)中的L代表从左往右分析 R代表构造一个最右推导的逆过程即最左规约 0代表向右查看0个字符
SLR(1)中的S代表简单的LR(1)分析 其他均一致 1代表向右查看一个字符来选择产生式
注意DFA那里记得加双箭头
第六章题型总结
注意分析表中的acc位置
SLR注意FOLLOW的求解,别出错,另外不明确说明的话默认用改进后的SLR吧
第七章 语法制导的语义计算
三万五千字长文!让你懂透编译原理(六)——第六章 属性文法和语法制导翻译_eqn翻译模式-CSDN博客
基于属性文法的语义计算
相关概念补充:
属性文法是在上下文无关文法(2型文法)的基础上,进行如下扩展:
为每个文法符号关联多个属性(Attribute)
为文法的每个产生式关联一个语义动作集合
该上下文语法为此属性文法的基础文法
可简单理解为左部的符号属性为综合属性,右部的符号属性为继承属性
建立语法分析树之后,每个节点用虚线连接自身属性并进行编号,之后根据语义动作构造有向边,即可获得一个属性依赖图
结合拓扑排序进行计算的最终结果如下
基于S-属性文法的语义计算
采用自底向上的方式进行语义计算
根据分析表可知采用的是SLR分析方法获得的
基于L-属性文法的语义计算
采用自顶向下的方式进行语义计算
翻译模式
基于S-翻译模式的语义计算
分析一下:栈顶指针top指向栈顶元素, 因此对应第1、3、5、7行top值没有改变,因为归约前后都是用左部的一个符号替换右部的一个符号,而第2、4、6行的归约会导致先后的top值发生改变,用一个左部符号替换右部三个符号,这里是用的规约前的top值来表示下标,这时的左部符号在栈中对应下标top - 2
第八章 静态语义分析和中间代码生成
中间代码的三种表示形式
TAC表示中op可以是任意2元以下的运算或操作,因此x,y,z中的每个位置都可能是空
TAC三地址码表示形式 x := y op z
抽象语法树AST和有向无圈图DAG的节点不包含()