首页 编译原理
文章
取消

编译原理

编译与计算机程序设计语言的关系

编译:将高级语言翻译成机器语言或汇编语言的过程

编译器和解释器的区别:

1.编译器整个将源程序翻译成目标程序,而解释器以源程序作为输入,每输入一条语句就执行一条语句,不生产目标程序

2.相比解释器编译器经过一次编译就不需要再编译,直接以目标程序运行即可

编译器在语言处理系统中的位置

编译器的位置

翻译过程

一条句子的翻译过程:

1.词法分析

划分词性,如名词、动词、形容词

2.语法分析

划分短语,依靠词法分析,如名词短语、动词短语、介词短语

3.语义分析

最后根据语法分析,总结各短语的关系,得到一条句子的语义(中间表示形式),如某人干了某事,即名词短语和动词短语的练习

语法制导翻译

即编译器过程的逻辑实现,在实际中可能是多个步骤在一起实现,比如语法分析、语义分析、中间代码生成就被放在一起实现,而这个技术也称为语法制导翻译

语法制导翻译

词法分析

1.这个过程概括来讲就是:高级语言的单词 –> 对应的机内表示(也被称为token),为了区别这些机内表示,token的结构如下:

token:(种别码,属性值),这很容易理解,种别码表示哪类词性,比如名词或是动词,而属性值就是这具体指哪个名词或是动词,而高级语言具体存在那些token,如下图:

词法分析

语法分析

文法:短语组合规则,即语法规则,毕竟短语是由各种单词构造的,所以文法指的就是各种token构造成语法的规则,或者说称它为生成语法的函数

语法分析:从词法分析器输出的token序列识别短语并构造语法分析树的过程,语法分析树描述了句子的语法结构

语义分析

高级语言大概分为声明语句和可执行语句,声明语句声明属性和对象,而属性和对象作为可执行语句的操作单元,重新赋值给新的属性或对象,而这些对象就属于token中的标识符,可见表示符是声明语句和可执行语句的重要组成部分

所以语义分析的第一任务是提取这些属性,并且记录它们的种属(比如简单变量、数组、函数或者叫过程)、类型、值、作用域、位置、长度

语义分析的第二任务是语义检查

中间表示形式

1.表现方式:三地址码和语法结构树

三地址码:由三地址指令序列组成,每条指令最多有三个操作数,所以被称为三地址码,三地址指令形如:

三地址码

三地址指令的数据结构表示分为:四元式(类似汇编语句,前缀表达式)、三元式,间接三元式

四元式形如:

四元式

如此可见,中间表示形式已经非常像汇编代码,而目标代码生成器最终的任务就是为变量或对象选择合适的寄存器

2.代码优化,分为机器无关代码优化器和机器相关代码优化器,在语言处理系统中所处的位置如下:

代码优化

词法语法分析基本概念

字母表:有穷字符集合,这个字符包括大小写字母,数字,标点符号等,用∑表示,其中用ε表示空串

字母表的基本运算:乘积、n次幂、正闭包、克林闭包

正闭包:长度为正数的串的集合,是无穷的,不断的进行递归的幂运算得到的集合

克林闭包与正闭包:克林闭包 equal (正闭包 并 空字母表)

串:克林闭包的每个元素,都被称为一个串

串的长度

串运算:连接,幂运算

文法定义

首先看下图:该图表示的是自然语言中句子的文法

自然语言句子文法

我们可以看到文法是由一条条产生式构成,如果我们把产生式作为函数,那么产生式左端就是函数参数,右端就是函数输出

如果把函数也作为属性,那么它表示就是产生规则,我们丢入原料就会输出产物,而把函数作为参数传给函数,就会形成更大的规则,甚至的递归的

因而上图表示的是句子的产生规则

其中未用尖括号的部分称为语言的基本符号(函数的最终输出),有括号的称为语法成分(函数)

终结符和非终结符

非终结符:表示语法成分的变量

非终结符

终结符:语言的基本符号,也叫token

以上两者统称文法符号

两者关系:

两者的交并运算

产生式

产生式左右端均是串,串是由终结符和非终结符组合而成,而产生式左端至少包含一个非终结符

产生式

开始符号

文法最终的输出

开始符号

符号约定

终结符:

终结符

非终结符:

非终结符

文法符号(以上两者均可以表示)、终结符号串、文法符号串:

文法符号

语言的定义

推导和规约

推导,按照产生式向右推导,最终得到只有终结符的式子,规约相反,向左推导,直至产生式左端是开始符号

通过这两个方法可以判断一个词串是否是该语言的句子

句型和句子

句型和句子

S =>* x,即开始符号经过若干推导步骤得到的产物就可称为句型,而只有终结符的句型才称为句子

短语、直接短语、句柄

看下文:

如何区别三者

语言

语言 所有句子构成的集合就被称为语言,即L(G) = {w|S =>* w,w∈VT* },其中VT* 表示终结符的克林闭包,w表示终结符号串

文法解决了无穷语言的有穷表示,函数的递归表示不也可以实现这样的效果吗,这也是我为什么把文法比作函数的原因,也可以对应到MIT-Scheme流和表的定义,两者都有递归性质

文法的分类

四种文法之间的关系:

四种文法之间的关系

正则文法

正如我们用L(G)表示语言,其中G表示文法,同时也可以用L(r)表示,其中r,就是正则表达式

1.正则文法(3型文法)包括右线性文法和左线性文法,即在上下文无关文法的基础上约束产生式右端要么是终结符号串,要么是非终结符和终结符号串的组合(文法符号串),而非终结符的位置则决定了是右线性文法还是左线性文法

2.正则文法示例: 使用正则文法

其实正则文法和正则表达式很像,只不过多了空串和表达式引用,其他如*(0个或多个字符),(或)等符号的意思是相同的

3.正则表达式,是正则文法的更加紧凑易读的写法,其实就是我们平时应用的正则表达式,而且这个*,其实就集合运算中的克林闭包;见下图:

正则表达式

4.运算符优先级:* 克林闭包 > 连接 >

5.正则定义

为了方便我们可以给正则表达式命名,并在其他正则表达式中使用这些标识符,构造一个更加复杂的正则表达式

正则定义

例子:

无符号数的正则定义

6.正则文法转正则表达式

基本思路:

基本思路

1
2
3
4
1.根据正则文法G构造正则表达式联立方程组
2.解联立方程组,求等价的正则表达式r
3.求得结果
    最终结果是 S=r的形式

如何构造方程组:

构造方程组

然后代入消元,解联立方程组,最终得到S=r的形式

CFG(上下文无关文法)的分析树

上下文无关文法相比正则文法使用更多,受到更多的研究并且相比正则文法能更熟练的表达句子,但是程序中的大多数单词可以用正则文法表示,下面是分析树的相关概念:

分析树的相关概念

句型的短语

句型的短语

二义性文法

一个文法可以为一个句子生成多颗分析树,则该句子存在二义性文法,二义性文法是不好的,我们可能需要引入非终结符来消除二义性,以便编译器能准确的翻译句子,或者定义一组规则,避免二义性文法,如:else与离它最近的if组合

二义性文法的判定

给定一个充分条件,满足这个条件则说明该文法无二义性,否则不能说明是否存在二义性

有穷自动机(FA)

它是一个数学模型,模型示意图如下:

FA模型示意图

用一句话表示这个模型就是,有穷控制器根据读头指向的输入,和当前状态,判断是否进入下一状态

有穷自动机可以通过转换图表示

转换图

FA接收的语言

对于串A,如果FA存在对应于串A的从初始状态到某个终止状态的转换序列,则称串A被FA接收,有FA所能接收的所有串组成的集合称为FA接收的语言,记为L(M)

最长子串匹配原则

最长子串匹配原则

简单来说就是匹配到最远的终止状态

FA的分类

FA又可以分为DFA、NFA,其中DFA表示确定的有穷自动机,即每读取一次输入,只会有一个输出,而NFA则可能有多个,相比于DFA,NFA更加直观,但程序实现更加复杂,DFA则相对容易

DFA的算法表示:

s = s0; // s0表示初始状态
c = nextChar(); // 读取下个输入
while(c != eof){
    s = move(s,c)  // ð函数,更新当前状态
    c = nextChar();
}
if(s在F中) return "yes"; // F代表终止状态集
else return "no";

如上,s0初始状态和F终止状态集是已知的,句子作为输入

正则文法、正则表达式、NFA、DFA之间的关系

正则文法 <==> 正则表达式 <==> NFA <==> DFA

我们最终需要的是DFA,因为其用程序更容易实现,因而更适合构造我们的词法分析器,而在求DFA时一般按照上面的顺序一步步得到DFA,但其实目前从正则表示式也可以很容易直接构造DFA,例如有如下的正则表达式:

(ab)* abb,我们可以把它分成四个部分,第一部分:(ab)* ,第二部分:a,第三部分:b,第四部分:b,按照从左往右的顺序,每连接一个部分就是一个新的句型,所以把每个句型作为FA模型中的状态,根据输入,判断是否应该转入下一句型,或是回溯到原来的句型,这样就能一步步构造出DFA了

NFA ==> DFA

NFA ==> DFA

上例中我们可以发现,DFA中每个状态都是由NFA中的状态构成的集合,而每接收一个输入,会作用到集合中每个状态,从而又形成新的状态集合

词法分析器的错误处理和错误恢复

词法分析器检查单词的步骤:词法错误检测 ==> 调用错误处理程序 ==> 错误处理 ==> 错误恢复策略

1.首先词法错误检测:如果当前状态遇到了不可能的输入,调用调用错误处理程序

2.错误处理程序查找已扫描的字符串中最后一个于某终态对应的字符,如找到,将该字符和前面的字符识别为一个正确的单词,未找到则报错,并启用错误恢复策略

3.错误恢复策略删除输入,知道遇到正确的字符开启下一轮错误检测,直至扫描完整个输入

语法分析

自顶向下的分析概述

从分析树的根节点(文法开始符号)向叶节点(一般时文法终结符)方向构造分析树,其实就是之前判断一个词串是否是文法的句子,从开始符号推导得到终结字符串的过程,也就是S ==>* w的过程

如何构造分析树:给定文法如下

1
2
3
4
5
6
7
8
文法
E --> E + E
E --> E * E
E --> (E)
E --> id

输入
id + (id + id)

其中我们发现每一步推导我么都需要做两个选择

  • 用该非终结符的哪个候选式进行替换
  • 替换当前句型中的哪个非终结符

其实以我们上帝视角来看这个词串其实很简单,把最左边的id,作为一个句型,(id+id)作为一个句型,就可发现它符合产生式:E –> E + E,然后(id + id)本身其实就符合产生式 E –> E + E,所以还是很简单的,计算机不像我们可以看到整个输入,计算机只能一个个读入

推导的方式

  • 最左推导
  • 最右推导

两种推导产生的语法分析树具有唯一性

自顶向下分析法总是采用最左推导

自顶向下总采用最推导

为什么会需要预测分析器

在自顶向下的递归下降分析法(计算机采用的自顶向下分析法)中,计算机根据输入选择候选式,如果该候选式不合适,就有必要回溯选择其他候选式,但回溯使得计算效率降低,所以我们通过预测分析器避免回溯,简单来说如果能够预测正确的候选式,就不会发生回溯了(听起来其实有点扯,什么叫做能够预测出正确式子,总之不急,慢慢来,继续学习下面的内容)

递归下降分析法

预测分析

预测分析

我们前面也提到过计算机不像人能提前看到后面的输入,它只能一个个读入输入,而预测分析器中提到的向前看k个输入符号估计就是为了能模仿人类这种分析的方式

文法转换

不是所有文法适合自顶向下分析法,所以就存在文法转换,将不合适的转换成合适的

回溯存在的例子:

回溯

即句型存在公共前缀,容器出现回溯,如果前者不合适就需要回溯选择下一个,这种情况的解决办法是提取左公因子 提取左公因子 另外如果有形如:A –> Aα 的文法,对于计算机很容易陷入无限循环,而这种文法被称为左递归文法,如下:

左递归文法

消除直接左递归

基本上是套公式,如图:

消除左递归公式

消除间接左递归

通过代入消元,将间接左递归转换成直接左递归,如下例:

消除间接左递归

本文由作者按照 CC BY 4.0 进行授权