编译器
阅读以某一种语言(源代码)编写的程序,并把该程序翻译成为一个等价的、用另一种语言(目标语言)编写的程序。
解释器
另一种常见的语言处理器,并不通过翻译的方式生成目标程序,直接利用用户提供的输入执行源程序中指定的操作。
预处理器:一个源程序可能被分割成多个模块,并存放于独立的文件中,把源程序聚合在一起的任务有时会由一个被成为预处理器的程序独立完成。
汇编器:对编译器产生的汇编语言程序进行处理,并生成可重定位的机器代码。
链接器/加载器:一个文件中的代码可能指向另一个文件中的位置,而链接器能够解决外部内存地址的问题。最后,加载器把所有的可执行目标文件放到内存中执行。
编译器的两个组成部分:分析部分和综合部分。
一个编译器的各个步骤:
词法分析/扫描:词法分析器读入组成源程序的字符流,并且将它们组织成为有意义的词素的序列。每个词素都被映射成如下语法单元,传送给语法分析:
分隔词素的空格会被词法分析器忽略掉。
语法分析/解析:语法分析器使用由词法分析器生成的各个词法单元的第一个分量来创建树形的中间表示。
语义分析器使用语法树和符号表中的信息来检查源程序是否和语言定义的语义一致。它同时也收集类型信息,并把这些信息存放在语法树或者符号表中,以便在随后的中间代码生成过程中使用。
三地址代码的中间表示形式。
代码优化
代码生成
代码生成器以源程序的中间表示形式作为输入,并把它映射到目标语言。
将多个步骤组合成趟
编译器构造工具
一个赋值语句的翻译:
最近嵌套规则
一个标识符x在最近声明x的作用域中,也就是说,从x出现的块开始,从内向外检查各个块时找到的第一个对x的声明。
符号表支持的三种操作:
词法分析是编译的第一阶段。词法分析器的主要任务是读入源程序的输入字符、将它们组成词素,生成并输出一个词法单元序列,每个词法单元序列对应于一个词素。
词法分析器在编译器中负责读取源程序,因此还会完成一些识别词素之外的其他任务。
有时,词法分析器也可以分成两个级联的处理阶段
编译的分析部分划分为词法分析和语法分析阶段的原因:
一个标识符(id)的属性值是一个指向符号表中该标识符对应条目的指针。
"恐慌模式"恢复:从剩余的输入中不断删除字符,直到词法分析器能够在剩余输入的开头发现一个正确的词法单元为止。
其他错误恢复动作:
加快源程序读入速度
每次对于forward指针需要检查两步:是否到达缓冲区末尾;确定读入字符是什么。(eof只能代表缓冲区末尾)
就是说,引入哨兵标记后每次只要检查forward指针的读入字符是什么即可。
正则表达式是一种用来描述词素模式的重要表示方法。
字母表:一个有限的符号集合
串:某个字母表中符号的一个有穷序列
空串:长度为0的串,用ε表示
语言:某个给定字母表上一个任意的可数的串集合
letter_表示任一字母或下划线,digit表示数位(任一单个数字)
前面的d为后面的d铺垫,方便后面使用,从而达到最后的d可以用∑和前面的d表示
示例:
我们可以使用两种方法来处理那些看起来像标识符的保留字:
示例:
Lex(词法分析器生成工具)将它的输入程序变成一个词法分析器转换的核心是被称为有穷自动机的表示方法。这些自动机在本质上是与状态转换图类似的图,但有如下几点不同:
确定的和不确定的有穷自动机能识别的语言的集合是相同的。事实上,这些语言的集合正好是能够用正则表达式描述的语言的集合。这个集合中的语言称为正则语言。
示例:
状态0可以由a到达状态0或状态1
示例:
只要存在某条[其标号序列为某符号串的]路径能够从开始状态到达某个接受状态,NFA就接受这个串。
路径中的ε标号将被忽略
DFA相比NFA,NFA的符号有空串ε且同一个符号可以由同个状态出发到达多个状态,DFA的符号没有空串符号ε且每个状态的每个符号的出边存在且唯一
示例:
NFA对于一个输入符号可以选择不同的转换,它还可以执行输入ε上的转换,甚至可以选择是对ε或是对真实的输入符号执行转换,因此对NFA的模拟不如对DFA的模拟直接。于是,我们需要将一个NFA转换为一个识别相同语言的DFA。
子集构造法
将NFA转换为DFA,通过子集构造法构造转换表Dtran得到DFA:
Dtran各行:NFA状态集–DFA状态–DFA各个出边
Dstates初始为ε-closure(s0),且未标记
当Dstates中没用未标记的状态时,即DFA的状态都被确定,停止循环就得到了DFA:
通过DFA的每个状态和其对应的出边即可画出DFA
示例:
算法总效率:O(k(n + m))
将正则表达式转化为一个NFA的McMaughton-Yamada-Thompson算法
基本规则:
归纳规则:
将正则表达式转化为NFA,先画出由表达式构成的语法分析树,最小到每个表达式不能再划分。每个最小的表达式由基本规则构造出NFA,然后四种情况(并、连接、闭包、括号)根据归纳规则合成最终表达式的NFA。
示例:
我们总是希望使用的DFA的状态数量尽可能地少。
只需要改变状态名字就可以将一个自动机转换成另一个自动机,我们就说这两个自动机是同构的。
任何正则语言都有一个唯一的(不计同构)状态数目最少的DFA。
从任意一个接受相同语言的DFA出发,通过分组合并等价的状态,我们总是可以构建得到这个状态数最少的DFA。
算法:
最小化一个DFA的状态数量,进行以下步骤:
示例:
示例1:
示例2:
用代码的位置来表示所处的状态,只能用于简单的情况,情况复杂时使用以下两种方法。注意:中括号[]的字符在程序中不要更新输入缓冲。
示例1:
示例2:
当终态还能接受字符时,通过改写DFA实现:
示例:
表驱动方法的优缺点:
语法分析器构造出一棵语法分析树,并把它传递给编译器的其他部分近一步处理。
句型:可能包含终结符号又包含非终结符号,也可能是空串
句子:不包含非终结符号的句型
上下文无关语言:由文法生成的语言
文法等价:两个文法生成相同语言
最右推导有时也称为规范推导。
语法分析树和最左推导/最右推导之间存在着一种一对一的关系。每一个语法分析树都和唯一的最左推导及最右推导相关联。(1对1+1)
即一个没有二义性的文法对一个句子只能有一个语法分析树、一个最左推导和一个最右推导。
文法是比正则表达式表达能力更强的表示方法。
每个可以使用正则表达式描述的构造都可以使用文法来描述,但是反之不成立。
正则表达式最适合描述诸如标识符、常量、关键字、空白等语言构造的结构。文法最适合描述嵌套结构,比如对称的括号对,匹配的begin-end,相互对应的if-then-else等。这些嵌套结构不能使用正则表达式描述。
优先级
结合性
左递归表示了左结合性,右递归表示了右结合性。
悬挂else
采用就近匹配原则,有else就会和前面没有被匹配的if进行匹配
自顶向下语法分析方法不能处理左递归文法,因此使用将左递归转换为右递归来消除左递归
就是把每个非终结符的产生式右侧的【每个序号在这个非终结符前面的非终结符】用它(序号在前的那个非终结符)的产生式替换掉它
提取左公因子是一种文法转换方法,它可以产生适用于预测分析技术或自顶向下分析技术的文法。
间接左因子常采用代入法:
自顶向下语法分析可以被看作是输入串构造语法分析树的问题,它从语法分析树的根节点开始,按照先根次序(深度优先),创建这棵语法分析树的各个节点。自顶向下语法分析也可以被看作寻找输入串的最左推导的过程。
自顶向下分析算法:
递归下降分析
将一个非终结符的文法规则看作是识别A的一个过程的定义。
就是说对文法中的每个非终结符都有一个(使用它的产生式右侧作为规则)识别它的程序,该程序中每个终结符都对应了match()函数进行输入匹配的检查;每个非终结符都对应了调用相应非终结符的程序;产生式中的选择对应程序代码中的case或者if结构。
示例:
由于左递归会使得程序不停的递归下去,所以需要使用EBNF{ }对文法进行改写才能使用自顶向下的分析方法
当有左因子时,通过EBNF[ ]改写文法
使用EBNF后通过程序处理运算顺序
在程序中构建语法树
FIRST集
计算文法非终结符的First集算法:
示例:
FOLLOW集
计算FOLLOW集算法:
示例:
注意:
判断一个串是否符合某LL(1)文法,通常使用LL(1)分析方法。LL(1)分析方法需要用到LL(1)分析表,而LL(1)分析表可以通过First集和Follow集构造。
LL(1)分析表
分析表中横向为所有的非终结符,$表示串结束符;纵向为所有终结符。
简单文法的LL(1)分析表构造:
即如果A不能推出空集,则只考虑第一条;如果可以推出空集,那么观察S 经 若 干 步 推 导 后 非 终 结 符 A 可 以 出 现 在 终 结 符 a ∗ ∗ ( 包 括 经若干步推导后非终结符A可以出现在终结符a**(包括 经若干步推导后非终结符A可以出现在终结符a∗∗(包括)**的前面,则将对应的A→α填入[A,a]中。
一般文法的LL(1)分析表构造(采用First和Follow集):
注意是观察First(α)
LL(1)文法
等价于:
LL(1)文法不允许一个格子包含两条产生式(即二义性),可以在表格标注采用一条产生式来消除二义性,使得文法能用LL(1)方法进行分析。
LL(1)分析
LL(1)分析过程
通常利用LL(1)分析表进行分析
栈和输入都只剩下$代表串是该文法可以接受的串。
LL(1)出错:栈顶与输入不匹配;串空栈不空。
示例:
自底向上的分析方法
自底向上分析的四个动作:
示例:
句柄
示例:
通过分析树找句柄:
用句柄来对句型进行归约:
归约过程就是依次找出分析树中的最左子树并去除叶子节点
LR(0)项
示例:
"."表示当前分析时符号在栈还是在输入串的情况
根据"."位置的不同将LR(0)项分四类:
LR(0)项的有限自动机
同时,还要有.X→X.的Goto连接
示例:
子集法构造DFA:
直接得到DFA:
总结
由文法得到NFA的步骤:
NFA没用结束状态,由分析程序决定结束
由文法得到DFA的两种方法:
Ⅰ.构造NFA,由子集构造法得到DFA
Ⅱ.不通过NFA,直接得到DFA:
注意DFA中的状态需要指明包含的NFA状态(LR(0)项)
LR(0)分析算法
LR(0)文法不允许某个状态中同时包含移进项和归约项(移进-归约冲突),也不允许包含两个归约项(归约-归约冲突)。移进-移进并不会冲突,因为会先移进再根据移进的字符判断转向下一个状态。
示例:
总结
用LR(0)方法分析一个串是否是某文法可识别的串,步骤为:
SLR(1)分析算法
SLR(1)文法不允许某个状态中同时存在满足条件的移进项和归约项,也不允许同时存在满足条件的两个归约项。
示例:
总结
用LR(0)方法分析一个串是否是某文法可识别的串,步骤为:
练习:
语法制导翻译
语法制导定义SDD
示例:
语法制导翻译方案SDT
SDD与SDT
综合属性和继承属性
S属性的SDD
注释语法分析树
示例:
依赖图
通常综合属性在文法符合右边,继承属性在文法符合左边,操作对应于虚节点
示例:
拓扑排序
示例:
属性求值顺序
S属性的SDD
L属性的SDD
注:不形成环路
示例:
后缀翻译方案
示例:
产生式内部带有语义动作的SDT
插入动作的语法分析树
将L-SDD转换为SDT
示例:
L属性定义的SDT实现
在递归下降语法分析过程中进行翻译
示例:
三地址代码中不能有双目以上的运算,也不允许出现组合运算(每条只能有一个运算)
常见三地址指令形式
示例:
三地址代码是中间代码的一种抽象形式,具体实现包括四元式、 三元式和间接三元式。
四元式
示例:
三元式
间接三元式
类型表达式
示例:
类型等价
类型的声明
局部变量名的存储布局
声明的序列
表达式中的运算
"||"是连接的作用
示例:
类型检查规则
类型转换
拓宽函数的适应
布尔表达式
短路代码
控制流语句
控制流语句的代码布局方案
布尔表达式的控制流翻译
示例: