赞
踩
编译器的核心功能是把源代码翻译成目标代码:
翻译!!!目标代码!!!
每个阶段将源程序从一种表示转换成另一种表示。
随着编译器各个阶段的进展,源程序的内部表示不断地发生变化。
串:是字母表中符号的一个有穷序列。
∑ 是一个字母表,任意 x∈∑*,x 是 ∑ 上的一个串。
设 ∑ 是一个字母表,任意L 属于 ∑*,L 称为字母表 ∑ 上的一个语言。
任意 x∈L,x 叫做 L 的一个句子。
文法是用于描述语言的语法结构的形式规则。
任何一种语言都有它自己的文法,不管它是机器语言还是自然语言。
文法可以定义为一个四元组(VT, VN, S, P)
推导:用产生式的右部替换产生式的左部
用图形的方式展示推导过程
二义性:多个语法分析树生成同一个终结符号串
任何语言实现的基础是语言定义
语言的定义决定了该语言具有什么样的语言功能、 什么样的程序结构、以及具体的使用形式等细节问题。
参数传递方法:
编译过程的分析部分:词法分析 + 语法分析
三种实现方式
若 r, s为正则表达式,表示语言 L ( r ) 和 L ( s ) L(r)和L(s) L(r)和L(s),则(从1到4优先级递增)
正则表达式等价(equivalent):r = s → 表示的语言相同, L ( r ) = L ( s ) L(r) = L(s) L(r)=L(s)
状态转换图(注意初态和终态)
数学模型,表示为五元组M = {S,Σ,δ,s0,F},其中
正则表达式(描述单词)
NFA(定义语言):存在缺点,同一符号或ε和其它符号 → 多义性,难以实现
DFA(适于计算机实现)
NFA 和 DFA 都可识别正则表达式,时-空折衷
词法分析器可用一组NFA来描述,每个NFA表示一个单词
构造规则:注意开始的箭头和结束的双圈
在自顶向上的语法分析方法中,分析的关键是:选择候选式
在自底向上的语法分析方法中,分析的关键是:寻找句柄
自顶向下分析器:递归下降法,LL(1)
自底向上分析器:LR()
LL 和 LR 都不能表示所有 CFG。
LR分析表种类:
LL,LR,可最快速度发现错误:可行前缀特性,viable-prefix property,当一个输入前缀不是语言中任何符号串前缀——发生错误。
推导:描述文法定义语言的过程,自顶向下构造语法分析树的精确描述。
两个CFG生成相同语言,两个CFG等价。
最左推导,最右推导。
语法树:推导的图示,但不体现推导过程的顺序。
一棵语法树对应多个推导,但是有唯一的最左推导和最右推导。
二义性文法存在某个句子对应多个语法树,多个最左(右)推导。
证明CFG G生成语言L:(数学归纳法)
正则表达式可描述的语言CFG均可描述。
RG 都可用 NFA 表示,CFG 都是 RG,所以 CFG 都可用 NFA 表示。
为什么还需要正则表达式?
设计 CFG:
CFG 的修改
CFG 优点:
问题:CFG 文法只能描述编程语言的大部分语法。(无法表示标识符必须在使用前定义,函数形参和实参数目应匹配)
缺点:
产生回溯的原因:进行推导时,若产生式存在多个候选式,选择哪个候选式进行推导存在不确定性。
消除回溯的基本原则:对文法的任何非终结符,若能根据当前读头下的符号,准确的选择一个候选式进行推导,那么回溯就可以消除。
FIRST(X1 X2 … Xn ) = FIRST (X1) “+”
FIRST(X2) if ε is in FIRST(X1) “+”
FIRST(X3) if ε is in FIRST(X2) “+”
…
FIRST(Xn) if ε is in FIRST(Xn-1)
注意:仅当对所有i,e∈FIRST(Xi),才将e加入FIRST(X1 X2 … Xn)
$加入FOLLOW(S),S为开始符号,$为输入串结束标记
若A → αBβ,则FIRST(β)中符号除ε外,均加入FOLLOW(B)
若A → αB 或 A → αBβ 且 β →* ε,FOLLOW(A)中所有符号加入FOLLOW(B)
e.g. id+id*id
若文法G的预测分析表M中不含有多重定义项,则称G为 LL(1)文法。且无二义性,无左递归。
考虑非终结符的同步单词集
预测分析表空位填入错误处理函数
预测分析法实现步骤
缺点:不是所有文法满足LL(1)要求
自底向上语法分析,是从输入符号串出发,试图把它归约成识别符号。
从图形上看,自底向上分析过程是以输入符号串作为末端结点符号串,向着根结点方向往上构造语法树,使识别符号正是该语法树的根结点。
归约:某产生式体相匹配的特定子串被替换为该产生式头部的非终结符号
自底向上分析是一个不断进行直接归约的过程。任何自底向上分析方法的关键是要找出这种句柄。
符号串的句柄是与某个产生式右部匹配的子串,应该归约为产生式左部(最右推导的逆过程)。
一个句型可有多个不同句柄,但是非多义性文法的最右句型有唯一句柄。
基本操作:移进、归约、接受、错误。
若B为非终结符,则 A→a · Bb 为待约项目。
LR分析方法:当前最广义的无回溯的“移进- 归约”方法。
优点:适用范围广;分析速度快;报错准确。
从逻辑上说,一个LR分析器包括两部分:一个总控程序和一张分析表。
解决同一项目集中的移进-归约冲突。
对于冲突项目集 Ii ={A→β1·bβ2,B→β·,C→β·},如果集合FOLLOW(B)和FOLLOW(C)不相交,而且不包含b,那么,当状态Ii面临任何输入符号a时,可采用如下“移进—归约”的决策。
①当a=b时,则移进,置ACTION[i,a]=Sj
②当a∈FOLLOW(B)时,置ACTION [i,a]=rj
③当a∈FOLLOW(C)时,置ACTION [i,a]=rm
④当a不属于三种情况之一,置ACTION [i,a]=“ERROR”
一般地,若一个项目集 Ii 含有多个移进项目和归约项目,例如
Ii={A1→α·a1β1,A2→α·a2β2,…,Am→α·amβm,B1→α·,B2→α·,…,Bn→α·}
如果集合{a1,a2,…,am},FOLLOW(B1),FOLLOW(B2),… FOLLOW(Bn)两两不相交时,可类似地根据不同的当前符号,对状态为i中的冲突动作进行区分。这种解决“移进—归约”冲突的方法称作SLR方法。
SLR(1)文法:对于给定的文法G,若按上述方法构造的分析表不含多重定义的元素,则称文法G是SLR(1)文法。
设有文法G
E→E+T|T
T→T*F|F
F→(E)|id
构造该文法SLR(1)分析表。
① 将文法G增广为G′,同时对每一产生式进行编号
(0)S′→E
(1)E→E+T
(2)E→T
(3)T→T*F
(4)T→F
(5)F→(E)
(6)F→id
②对G′构造文法LR(0)项目集规范族如下:
③ 取这些项目集作为各状态,并根据转换函数GO画出识别文法G′的有穷自动机,
④ 用SLR方法解决“移进—归约”冲突。
在十二个项目集中, I1、 I2 和 I9 都含有“移进—归约”冲突,其解决办法是:
对于项目集 I1 ={S′→E·,E →E·+T},由于集合 FOLLOW(S′)={$}与集合{+}不相交,所以当状态为1时,面临着输入符号为+时便移进,而面临着输入符号为$时,则按产生式S′→E归约。对于项目集 I2 ={E→T·,T→T·*F},由于集合 FOLLOW(E)={+,),$}与集合{*}不相交,因此状态2面临输入符号为*时移进,而面临输入符号为+或)或$时,按产生式E→T归约。对于项目集I9 ={E →E+T·,T →T·*F},同样由于 FOLLOW(E) = { +, ), $ }与集合{*}不相交,因此状态9面临着输入符号为*时移进,面临着输入符号为+或)或$ 时,按产生式E→E+T归约。
⑤ 构造SLR(1)分析表
步骤 | 状态栈 | 符号栈 | 输入串 | 分析动作 | 下一状态 |
---|---|---|---|---|---|
1 | 0 | $ | id+id*id$ | S5 | 5 |
2 | 05 | $id | +id*id$ | r6 | GOTO[0,F]=3 |
3 | 03 | $F | +id*id$ | r4 | GOTO[0,T]=2 |
4 | 02 | $T | +id*id$ | r2 | GOTO[0,E]=1 |
5 | 01 | $E | +id*id$ | S6 | 6 |
6 | 016 | $E+ | id*id$ | S5 | 5 |
7 | 0165 | $E+id | *id$ | r6 | GOTO[6,F]=3 |
8 | 0163 | $E+F | *id$ | r4 | GOTO[6,T]=9 |
9 | 0169 | $E+T | *id$ | S7 | 7 |
10 | 01697 | $E+T* | id$ | S5 | 5 |
11 | 016975 | $E+T*id | $ | r6 | GOTO[7,F]=10 |
12 | 0169710 | $E+T*F | $ | r3 | GOTO[6,T]=9 |
13 | 0169 | $E+T | $ | r1 | GOTO[0,E]=1 |
14 | 01 | $E | $ | acc |
SLR(1)也存在不足,即如果冲突项目的非终结符FOLLOW集与有关集合相交时,就不能用SLR(1)方法解决。
SLR(1)是 LR(1)的一种特例,即所有 SLR(1)文法都是 LR(1)文法。
SLR只是简单地考察下一个输入符号b是否属于与归约项目A→α相关联的FOLLOW(A),但b∈FOLLOW(A)只是归约α的一个必要条件,而非充分条件。
对于产生式A→α的归约,在不同的使用位置,A会要求不同的后继符号。在特定位置,A的后继符集合是FOLLOW(A)的子集。
**任何二义性文法都不是LR(k)文法。 **
LALR分析法与SLR相类似,但功能比SLR(1)强,比LR(1)弱。
首先构造LR(1)项目集,如果它不存在冲突,就把同心集合并在一起,若合并后项目集规范族不存在“归约—归约”冲突,就按照这个集族构造分析表。``
一个LR(1)文法合并同心集后若不是LALR(1)文法,则可能存在归约/归约冲突,不可能存在移进/归约冲突。
语法制导翻译使用CFG来引导对语言的翻译,是一种面向文法的翻译技术。
语义分析器的任务:
① 检查各个语法结构的静态语义 (静态语义分析或静态检查 )
② 执行所规定的语义动作:如表达式的求值、符号表的填写、中间代码的生成
将静态检查和中间代码生成结合到语法分析中进行的技术称为语法制导翻译。
语法制导的翻译:一种形式化的语义描述方法,包括两种具体形式:
语法制导定义是一种接近形式化的语义描述方法。语法制导定义的表示分两部分:
属性的特点:
综合属性(synthesized attribute):
继承属性(inherited attribute)
副作用:一般属性值计算(基于属性值或常量进行的)之外的功能,如,打印出一个结果;将信息加入符号表。
注释分析树:将属性附着在分析树对应文法符号上(属性作为分析树的注释)。
仅仅使用综合属性的 SDD 称为 S 属性的 SDD。
如果一个SDD是S属性的,可以按照语法分析树节点的任何自底向上顺序来计算它的各个属性值。
L-属性定义的直观含义:在一个产生式所关联的各属性之间,依赖图的边可以从左到右,但不能从右到左。即 A->X1X2……Xn 中 Xi 的每个继承属性仅依赖于:
每个S-属性定义都是L-属性定义。
把L-属性定义变换成翻译模式,在构造翻译程序的过程中前进了一大步。
val 为综合属性,计算其值都在产生式的末尾。
L → En { print (E.val); }
E → E1 + T { E.val = E1 .val + T.val;}
E → T { E.val = T.val;}
T → T1 * F { T.val = T1.val * F.val ;}
T → F { T.val = F.val; }
F → (E) { F.val = E.val; }
F → digit { F.val = digit.lexval; }
为文法
S → ( L ) | a
L → L , S | S
写一个翻译方案,它输出每个a的嵌套深度。例如,对于( a , ( a , a) ),输出的结果是1 2 2。
depth 为继承属性,计算其值在紧靠非终结符出现之前。
S’→ {S. depth = 0 } S
S → {L. depth = S. depth + 1 } ( L )
S → a {print (S. depth) }
L → {L1. depth = L. depth }L1 , {S. depth = L. depth }S
L → {S. depth = L. depth }S
有文法G及其语法制导翻译如下所示:(语义规则中的*和+为运算符乘号和加号)
E → E1^T{E.val=E1.val * T.val}
E → T{E.val= T.val}
T → T1#n{T.val=T1.val +n.val}
T → n{T.val= n.val}
采用自底向下分析时,句子3∧3#4其值为:21
为文法
S → ( L ) | a
L → L , S | S
写一个翻译方案,它打印出每个a在句子中是第几个字符。例如,当句子是( a , ( a , ( a , a ) , (a) ) )时,打印的结果是2 5 8 10 14。
in 为继承属性,out 为综合属性。
每个非终结符左侧计算其 in 属性,每个产生式最右侧计算其 out 属性。
S’ → {S. in = 0 } S
S → {L. in = S. in + 1 } ( L )
{S. out = L. out + 1 }
S → a {S. out = S. in + 1; print (S. out) }
L → {L1. in = L. in }L1 ,
{S. in = L1. out + 1 } S
{L. out = S. out }
L → {S. in = L. in }S {L. out = S. out }
为文法
S → ( L ) | a
L → L , S | S
写一个翻译方案,它输出括号的对数。
num 为综合属性。
S’ → S {print (S. num)}
S → ( L ) { S. num = L.num + 1}
S → a {S. num = 0}
L → L1 , S { L. num = L1. num + S. num}
L → S { L. num = S.num }
“中间代码生成”程序的任务是:把经过语法分析和语义分析而获得的源程序中间表示翻译为中间代码表示。
方法:语法制导翻译。
采用独立于机器的中间代码的好处:
优点: 容易为不同目标机器开发不同后端
缺点: 编译过程变慢 (因为中间步骤)
中间表示:
抽象语法树(或者简称为语法树)反映了抽象的语法结构,而语法分析树(分析树)反映的是具体的语法结构。语法树是分析树的抽象形式或压缩形式。
三元式可以避免引入临时变量,使用获得变量值的位置来引用前面的运算结果。
包含一个指向三元式的指针列表,而不是列出三元式序列本身。
类型检查:利用一组逻辑规则来确定程序在运行时的行为,保证运算分量的类型和运算符的预期类型匹配。
翻译时的应用:
int[2][3] array(2,array(3,integer))
int *f(char a, char b) (char×char) → pointer(integer)
typedef struct person={
char name[8];
int sex;
int age;
}
struct person table[50];
person record( (name × array(8,char)) × (sex × integer) × (age × integer))
table array(50,person)
类型等价:结构等价 + 名等价
结构等价:满足以下条件之一:(类型名被类型表达式所代替,if 替换所有名字后,两个类型表达式结构上等价即结构等价)
T → B { t = B.type; w = B.width;}
C { T.type = C.type; T.width = C.width;}
B → int { B.type = integer; B.width = 4;}
B → float { B.type = float; B.width = 8;}
C → ε { C.type = t; C.width = w;}
C → [num] C1 { C.type = array( num.vale, C1.type);
C.width = num.value × C1.width;}
运行时刻环境
主题
静态分配
动态分配
堆空间,用于存放生命周期不确定、或生存到被明确删除为止的数据对象。
存储管理器基本功能
评价存储管理器的特性:
两大问题:
其他问题
活动记录静态分配:每个过程静态地分配一个数据区域,开始位置用staticArea表示。
活动记录栈式分配:
中间代码的流图表示法
流图可作为优化的基础
划分基本块的算法:
确定首指令(基本块的第一个指令)符合以下任一条:
确定基本块
基本块划分示例:
重要子函数:getReg(I)
代码生成时必须更新寄存器描述符和地址描述符。
通过一些相对低层的语义等价转换来优化代码。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。