一,什么叫编译程序?
编译器就是一个程序,它读入用某种语言编写的源程序,并翻译成一个与之等价的另一种语言编写的源程序。
编译程序的工作,从输入源程序开始,到输出目标程序结束,与自然语言之间的翻译有很多相似之处:
一段英文翻译成中文,需经下列步骤:
I am a hard-working student.
识别出句子中的单词->词法分析
分析句子的语法结构->语法分析
根据句子的含义进行初步分析->语义分析及中间代码生成
对译文进行修饰->代码优化
1词法分析
读入源程序 识别出单词 并用记号方式表示识别出的单词
2语法分析
在词法分析的基础上,根据语言的语法规则,把单词符号串组成各类语法单位.即在单词流的基础上建立一个层次结构-----建立语法树
3语义分析阶段
语义分析利用语法分析阶段确定的层次结构来识别表达式和语句中的操作信息及类型信息
4中间代码生成阶段
这种中间表示可以看成是某种抽象的程序,通常是与平台无关的
要易于产生,而且便于翻译成目标程序
编译各阶段的分组
前端和后端:
前端包括词法分析、词法分析、语义分析,以及相关的错误处理和符号表的建立,依赖于源程序并在很大程度上独立于目标机器。
后端主要包括代码优化、代码生成和相关错误处理。依赖于目标机器。
后端处理对象是由前端产生的结果,即中间代码。
遍:对源程序或源程序的中间结果从头到尾扫描一次,并做有关的加工处理,生成新的中间结果或目标程序。
二,高级语言及其语法描述
程序语言包括语法和语义。
高级语言的特性包括分类、程序结构、数据类型与操作、语句与控制结构等。
程序语言的语法描述是重点:
1,上下文无关文法
文法是描述语言的语法结构的形式规则。
它所定义的语法范畴(或语法单位)是完全独立于这种范畴可能出现的环境的。
一个上下文无关文法G包括四个组成部分:一组终结符号,一组非终结符,一个开始符号,以及一组产生式。
形式上定义一个上下文无关文法G是一个四元式
(VT,VN,S,P):
VT是一个非空有限集,它的每一个元素称为终结符号;
VN是一个非空有限集,它的每一个元素称为非终结符号,
VT∩VN=空集;
S是一个非终结符号,称为开始符号;
P是一个产生式(有限)集合,每个产生式形式是A→α,其中,P∈VN,α ∈( VT∪VN)*
开始符号S至少必须在某一产生式的左部出现一次。
最左(最右)推导:在推导的任何一步αβ,其中α、β是句型,都是对α中的最左(右)非终结符进行替换
2,语法分析树和二义性
语法树的根结由开始符号所标记;随着推导的展开,当某个非终结符被它的某个候选式所替换时,这个非终结符的相应结就产生了下一代新结点。每个新结点和其父亲结点间都有一条连线。
在一棵语法树生长过程中的任何时刻,所有那些没有后代的端末结自左至右排列起来就是一个句型。
如果一个文法存在某个句子对应两棵不同的语法树,则称这个文法是二义的。
3,形式语言分类
三,词法分析
从左至右逐个字符的对源程序进行扫描,产生一个个的单词符号,把作为字符串的源程序改造成为由单词符号串组成的程序。
词法分析器输出的单词符号常常用二元式来表示:<单词种别,单词符号的属性值>
词法分析器的设计上,巧妙的是超前搜索和状态转换图。
正规表达式与有限自动机:
重点是以下过程:
1把句子表示成正规式
2 把正规表达式表示成状态图即NFA
4对DFA进行化简
①对M的状态集S进行划分:
把S的终态和非终态分开,分成终态集合非终态集,形成基本分划П,显然这两个子集是可区别的。
②假定到某个时候П含有m个子集,
记П={I(1),I(2),… I(m)}
并且,属于不同子集的状态是可区别的。
检查П中的每个I(i)看能否进一步划分:
对于某个I(i)
另I(i)={q1 ,q2 ,…,qk}
若存在一个输入字符a使得I(i)a不全包含在现行П的某个子集I(j)中,就将I(i)一分为二
③一般地,若I(i)a落入现行П中N个不同子集,则应将I(i)划分为N个不相交的组,使得每个组J的Ja都落入П的同一子集,这样再形成新的分划
④重复上述过程,直至分划中所含的子集数不再增长为止。至此,П中的每个子集已不可再分。也就是说,每个子集中的状态是互相等价的,而不同子集中的状态则是互相可区别的。
⑤经过上述过程后,得到一个最后分划П.对于这个П中的每个子集,选取子集中的一个状态代表其它状态。
四,语法分析-自上而下分析法
它的任务是在词法分析识别出单词符号串的基础上,分析并判定程序的语法结构是否符合语法规则。
从文法的起始符出发进行句子的推导,即自上而下的分析;从句子本身出发,进行归约,看能否把句子规约为到起始符,即自下而上的规约
自上而下分析面临的问题:
文法的左递归问题,回溯的不确定性,虚假匹配的问题,不能准确地确定输入串中出错的位置
LL(1)分析法:
1,消除左递归
设有产生式
P→Pα|β (1)
其中β不以P开头,α不为ε。那么,我们可以把P的规则改为如下的非直接左递归形式:
P→βP’
P’→αP’|ε (2)
2,判断LL(1)文法
(1)文法不含左递归
(2)对于文法中每一个非终结符A的各个产生式的候选式的FIRST集两两不相交。即,若
A→α1|α2|…|αn
则 FIRST(αi)∩FIRST(αj)=Φ (i≠j)
(3)对于文法中的每个非终结符A,若它的某个候选首符集包含ε,则
FIRST(A)∩FOLLOW(A)=Φ
3,预测分析程序
构造预测分析表:
求出每个非终结符的FIRST集和FOLLOW集
对文法G的每个产生式, A->α,进行下面的处理
对每个终结符a,如果a属于FIRST(α),则把该产生式写入到M[A,a]
若ε属于FIRST(α),则对任何b属于FOLLOW(A), 把该产生式加入到M[A,b]
所有无定义的M[A,a]标上出错标志
五,语法分析-自下而上分析
自下而上分析的基本问题:
1,基本思想
用一个寄存符号的先进后出栈,把输入符号一个一个地移进到栈里,当栈顶形成某个产生式的候选式时,即把栈顶的这一部分替换成(归约为)该产生式的左部符号。
2,规范归约
定义:假定是文法G的一个句子,我们称序列
αn, αn-1,... α0
是α的一个规范归约,如果此序列满足:
(1) αn= α
(2) α0为文法的开始符号,即α0=S
(3) 对任何i,0 < i <= n, αi-1是从αi经把句柄替换成为相应产生式左部符号而得到的
3,自下而上的语法分析是使用符号栈的,同时规约过程可在语法树中体现。
算符优先分析法:
一个文法,如果它的任一产生式的右部都不含两个相继(并列)的非终结符,即不含如下形式的产生式右部:
…QR…则我们称该文法为算符文法。
构造算符优先表:
(1)通过检查产生式的每一个候选式可以找出满足a=.b
(即P→…ab…或P→…aQb…的产生式)
(2)为了满足<.和>.,需对G中每个非终结符P构造两个集合FIRSTVT(P)和LASTVT(P)
(3)构造集合FIRSTVT(P)的算法
按其定义,可用下面两条规则来构造集合FIRSTVT(P):
① 若有产生式P→a…或P→Qa…,
则a∈FIRSTVT(P);
② 若aFIRSTVT(Q),且有产生式P→Q…,
则a∈FIRSTVT(P)。
(4)同理构造构造集合LASTVT(P)的算法
按其定义,可用下面两条规则来构造集合LASTVT(P):
① 若有产生式P→… a或P→… aQ ,
则a LASTVT(P);
② 若a∈LASTVT(Q),且有产生式P→… Q ,
则a∈ LASTVT(P)。
(5)有了这两个集合之后,就可以通过检查每个产生式的候选式确定满足关系<.和>.的所有终结符对。
(1)假定有个产生式的一个候选形为
…aP…
那么,对任何b∈FIRSTVT(P),有a <. b。
(2)假定有个产生式的一个候选形为
…Pb…
那么,对任何a∈LASTVT(P),有a >. b。
LR分析方法:
1,构造LR(0)项目族集:
构造G的拓广文法G’;
构造I的闭包CLOSURE(I):(1) I的任何项目都属于CLOSURE(I);
(2) 若A→·B属于CLOSURE(I),那么,对任何关于B的产生式B→的项目B→·也属于CLOSURE(I);
(3) 重复执行上述两步骤直至CLOSURE(I) 不再增大为止
转换函数:定义转换函数如下:
GOTO(I,X)=CLOSURE(J)其中:
I为包含某一项目集的状态,
X为一文法符号,
J={任何形如A→X•的项目|A→•X属于I}。
再由转换函数建立状态之间的连接关系得到识别活前缀的DFA。
2.构造LR(0)分析表:
a)若项目A→•a属于Ik,且转换函数GO(Ik,a)=Ij,当a为终结符时 ,则置ACTION[k,a]为Sj。
b)若项目A→•属于Ik, 则对a为任何终结符或#′,置ACTION[k,a]=rj, j为产生式在文法G′中的编号。
c)若GO(Ik,A)=Ij,则置GOTO[k,A]=j,其中A为非终结符,j为某一状态号。
d)若项目S′→S•属于Ik,则置ACTION[k,#]= acc。
e)其它填上“报错标志”。
3.根据LR(0)分析表的分析方法:
<1>. 移进
把(s,a)的下一状态s’=GOTO[s,a] 和输入符号a推进栈,下一输入符号变成现行输入符号.
<2>. 归约
指用某产生式A进行归约. 假若的长度为r, 归约动作是A, 去除栈顶r个项,使状态sm-r变成栈顶状态,然后把(sm-r, A)的下一状态s’=GOTO[sm-r, A]和文法符号A推进栈.
<3>. 接受 宣布分析成功,停止分析器工作。
<4>. 报错 发现源程序含有错误,调用出错处理程序
SLR分析方法:
思想:大多数程序设计语言的文法不能满足 LR(0)文法的条件,即其规范族中会有含有冲突的项目集(状态)。对于有冲突的状态,向前查看一个符号,以确定采用的动作。
各分析方法的分析表构造方法类似
六,属性文法和语法制导翻译
属性文法的属性分为综合属性和继承属性。
基于属性文法的处理方法:
主要掌握依赖图和抽象语法树。
L-属性文法和自顶向下翻译:
翻译模式设计:(1)只需要综合属性的情况
为每一个语义规则建立一个包含赋值的动作,并把这个动作放在相应的产生式右边的末尾。
(2)既有综合属性又有继承属性的情况
①产生式右边的符号的继承属性必须在这个符号以前的动作中计算出来。
②一个动作不能引用这个动作右边符号的综合属性。
③产生式左边非终结符号的综合属性只有在它所引用的所有属性都计算出来以后才能计算。计算这种属性的动作通常可放在产生式右端的未尾。
七,语义分析和中间代码产生
中间语言的几种形式:逆波兰式、DAG图法、抽象语法树、三地址码(四元式、三元式、间接三元式)。
语句翻译成四元式:
1,数组元素的表达:
数组元素的地址计算公式
若数组A的元素存放在一片连续单元里,则可以较容易的访问数组的每个元素。假定数组的每个元素的宽度为w,则一维数组A[i] 这个元素的起始地址为:
base + (i – low)*w
其中,low为数组下标的下界, base是分配给数组的相对地址,即base为A的第一个元素A[low]的相对地址。
base + (i – low)*w 可整理为: i*w + (base –low*w)
若二维数组A按行存放,则可用如下公式计算A[i1,i2]的相对地址:
base + (( i1 – l1)*d2 + i2 – l2) *w
其中,l1、l2分别为i1、i2的下界;di界差。若ui为i的上界,则di=ui – li +1.
假定i1,i2是编译时唯一尚未知道的值,我们可以重写上述表达式为:( base –( (l1 *d2) + l2) *w)+ ( (i1*d2) + i2) *w
,2,布尔表达式的翻译:
A∨B 可理解为 if A then true else B
A∧B 可理解为 if A then B else false
┐A 可理解为 if A then false else true
(jnz,a,--,p) 表示 if a goto p
(jrop,x,y,p) 表示 if x rop y goto p
(j,--,--,p) 表示 goto p
3,循环和分支语句的翻译
循环语句
大多数程序语言中都有如下形式的循环句:
S→for i:=E1 step E2 until E3 do S1
其语义各语言可能有所不同,主要区别在先判断、后执行还是先执行、后判断。
分情况语句
各种语言的分支语句不尽相同(case,switch等),这里我们假定其形式为下所示:
case E of
C1 : S1;
C2 : S2;
…
Cn-1 : Sn-1;
otherwise : Sn
end;
翻译成:
T:=E;
L1: if T≠C1 goto L2;
S1; goto next;
L2: if T≠C2 goto L3;
S2; goto next;
L3:
…
Ln: Sn;
next: …
以上是对这门课重点内容的总结,到此大致讲述了从输入语句到中间代码的过程,要进一步理解,还需实现代码。希望之后有机会能实现。感谢费老师的精彩讲授!