前言
参考课上PPT内容。 该学习笔记目前仅打算个人使用。
后续会进一步整理,包括添加笔记内容,标明参考资料。
更新中。。。
目录
导言
词法分析,语法分析:
- 解决单词和语言成分的识别及词法和语法结构的检查。语法结构可形式化地用一组产生式来描述。给定一组产生式,我们应该能够将其分析器构造出来。
本章要介绍的是语义分析和代码生成技术。
程序语言的语义形式化描述目前有三种基本描述方法:
- 操作语义
- 指称语义
- 公理语义
一、翻译文法和语法制导翻译
例:有上下无关文法 G[E]:
1、E → E + T 2、E → T
3、T → T * F 4、T → F
5、F → ( E ) 6、F → i
- 此文法是一个中缀算术表达式文法
翻译的任务是:中缀表达式 → 逆波兰表示(后缀表达式):
- a + b * c → a b c * +
只需在上述文法中插入相应的动作符号。
1、E → E + T @+ 2、E → T
3、T → T * F @* 4、T → F
5、F → ( E ) 6、F → i @i
- @+,@*,@i 为动作符号。
- @为动作符号标记,其后为字符串。
在该例中,其对应语义子程序的功能是要输出打印动作符号标记后面的字符串。
所以:
产生式1: E → E + T @+ 的语义是分析 E,+ 和 T 输出 +
产生式6: F → i @i 的语义是分析 i 输出 i
输入文法
- 未插入动作符号时的文法。
- 由输入文法可以通过推导产生输入序列。
翻译文法
插入动作符号的文法。
翻译文法是上下文无关文法(2型文法)。
终结符号集由输入符号和动作符号组成。
由翻译文法可以通过推导产生活动序列。由翻译文法所产生的终结符号串称为活动序列。
活动序列
由翻译文法推导出的符号串,由终结符和动作符号组成。
- 输入序列
- 动作序列
例: ( i + i ) * i
可以用输入文法推导:
1、E → E + T 2、E → T
3、T → T * F 4、T → F
5、F → ( E ) 6、F → i
E ⇒ T ⇒ T * F ⇒ F * F ⇒ ( E ) * F ⇒ ( E + T ) * F ⇒ ( i + i ) * i
用相应的翻译文法推导:
1、E → E + T @+ 2、E → T
3、T → T * F @* 4、T → F
5、F → ( E ) 6、F → i @i
E ⇒ T ⇒ T * F @* ⇒ F * F @* ⇒ ( E ) * F @* ⇒ ( E + T @+ ) * F @* ⇒ ∗ \stackrel{*}{\Rightarrow}⇒∗ ( i @i + i @i @+) * i @i @*
从活动序列中,抽去动作符号则得输入序列:
( i + i ) * i
从活动序列中,抽去输入序列,则得动作序列。执行动作序列,则完成翻译任务:
@i @i @+ @i @* ⇒ i i + i *
以上例题中的翻译文法为:
GT = ( Vn, Vt, P, E )
Vn = { E, T, F }
Vt = { i, +, *, ( , ), @+, @*, @i }
P = { E → E + T @+, E → T, T → T * F @*, T → F, F → ( E ), F → i @i }
符号串翻译文法
输入文法中的动作符号对应的语义子程序是输出动作符号标记@后的字符串的文法
语法制导翻译
- 按翻译文法进行的翻译
给定一输入符号串,根据翻译文法获得翻译该符号串的动作序列,并执行该序列所规定的动作过程。
实现方法
在文法的适当位置插入语义动作符号。当按文法分析到动作符号时就调用相应的语义子程序,完成翻译任务。
翻译文法所定义的翻译是由输入序列和动作序列组成的对偶集。
如:
( i + i ) * i,@i @i @+ @i @* → i i+ i *
i + i * i,@i @i @i @* @+ → i i i *+
因此,给定一个翻译文法,就给定了一个对偶集。
二、属性翻译文法
在翻译文法的基础上,我们可以进一步定义属性文法。
翻译文法中的符号,包括终结符、非终结符和动作符号均可带有属性,这样能更好地描述和实现编译过程。
属性可以分为两种:
- 综合属性
- 继承属性
1、综合属性
求值规则
自右向左,自底向上。
例:基本操作数带有属性的表达式文法G[E]
E → E + T E → T
T → T * F T → F
F → ( E ) F → i↑c
其中↑c是综合属性符号,↑为综合属性标记, c为属性变量或者属性值。
此文法能够产生如下的输入序列:
( i↑3+i ↑9 ) * i↑2
根据给定的文法,可写出该输入序列的语法树
为了形式地表示上述表达式的属性求值过程,我们可以改写上述文法:
产生式 求值规则
- E↑p4 → E↑q5 + T↑r2 (q5 := p3;) p4 := q5 + r2;
- E↑p3 → T↑q4 p3 := q4;
- T↑p2 → T↑q3 * F↑r1 p2 := q3 * r1;
- T↑p2 → F↑q2 p2 := q2;
- F↑p1 → ( E↑q1 ) p1 := q1;
- F↑p1 → i↑q1 p1 := q1;
- p,q,r 为属性变量名
- 属性变量名局限于每个产生式,可使用不同的名字。
- 求值规则:自右向左,自底向上。
2、继承属性
求值规则
自左向右,自顶向下。
例:考虑到下列文法: G [<说明>]:
- <说明> → Type id <变量表>
- <变量表> → , id <变量表>
- <变量表> → ε
- Type:类型名,值为integer,real,boolean等,词法分析程序返回的类型的类别码。
- id: 变量(值:标识符本身)
对于上述文法的说明语句: integer A, B1
该文法的翻译任务:将声明的变量填入符号表(语义)
完成该工作的动作符号: @set_table

翻译文法:
- <说明> → Type id @set_table <变量表>
- <变量表> → ,id @set_table <变量表>
- <变量表> → ε
- @set_table的插入位置表示填表动作的时机
填表时需要的信息:类型、名字、以及位置(可以用全程变量的指针)如何得到?
终结符(输入符号)的类型和名字在词法分析时得到,可设两个综合属性。
- Type↑t:t 中是类型值
- id↑n:n 是变量名
填表动作符号也可带有属性:
- @set_table↓t1,n1:↓t1,n1可从其前面的符号得到,称为继承属性,继承前面符号的值。
- <变量表>↓t2:↓t2 同上
属性翻译文法:
- <说明> → Type↑t id↑n @set_table↓t1,n1 <变量表>↓t2 t2,t1 := t; n1 := n;
- <变量表>↓t2 →, id↑n @set_table↓t1,n1 <变量表>↓t3 t3,t1 := t2; n1:= n;
- <变量表>↓t1 → ε
例: int A, BC → Type↑int id↑A , id↑BC
语法树:
int A, BC 的分析翻译过程:
<说明> ⇒ Type↑t id ↑n1 @set_table↓t,n1 <变量表>↓t ⇒ + \stackrel{+}{\Rightarrow}⇒+ Type↑t id ↑n1 @set_table↓t,n1 , id↑n2 @set_table↓t,n2

属性文法的自顶向下翻译
L-属性翻译文法( L-ATG)
- 这是属性翻译文法中较简单的一种。其输入文法要求是 LL(1)文法,可用自顶向下分析方法构造分析器。在分析过程中可进行属性求值。
特点:
- 某个符号的继承属性只依赖于该符号左边的信息。
定义
L-属性翻译文法是带有下列说明的翻译文法:
文法中的终结符,非终结符及动作符号都带有属性,且每个属性都有一个值域。
非终结符及动作符号的属性可分为继承属性和综合属性。
开始符号的继承属性具有指定的初始值。
输入符号(终结符号)的每个综合属性具有指定的初始值。
属性值的求值规则如下:
继承属性:体现自顶向下,自左向右的求值特性。
- 产生式左部非终结符号的继承属性值,取前面产生式右部该符号已有的继承属性值。
- 产生式右部符号的继承属性值,用该产生式左部符号的继承属性或出现在该符号左部的符号的属性值进行计算。
综合属性:体现自底向上,自右向左的求值特性。
- 产生式右部非终结符号的综合属性值,取其下部产生式左部同名非终结符号的综合属性值。
- 产生式左部非终结符号的综合属性值,用该产生式左部符号的继承属性或某些右部符号的(任意)属性进行计算。
- 动作符号的综合属性用该符号的继承属性或某些右部符号的(任意)属性进行计算。
例: A → BC
求值顺序:
A的继承属性
若A为开始符号,则有指定值;否则由上面产生式右部符号为A的继承属性求得
B的继承属性
由A的继承属性求得
B的综合属性
由下面产生式中左部符号为B的综合属性求得
C的继承属性
由A的继承属性和B的属性求得
C的综合属性
由下面产生式中左部符号为C的综合属性求得
A的综合属性
由A的继承属性或产生式某些右部符号属性求得
- 产生式左部非终结符号的综合属性值,用该产生式左部符号的继承属性或某些右部符号的(任意)属性进行计算。
注意:
- 终结符只有综合属性(它们由词法分析器提供)。
- 非终结符和动作符号既可以有综合属性也可有继承属性。
- 文法开始符号的所有继承属性作为属性计算前的初始值。一般来说,对出现在产生式右边的继承属性和出现在产生式左边的综合属性都必须提供一个求值规则。
属性求值规则中只能使用相应产生式中的文法符号的属性(这有助于在产生式范围内“封装”属性的依赖性)。
但出现在产生式左边的继承属性和出现在产生式右边的综合属性不由所给的产生式的属性求值规则进行计算。它们由其它产生式的属性规则计算。
简单赋值形式的L-属性翻译文法(SL-ATG)
一般情况 :
x := f (y, z)
x的属性值是y和z的属性值的函数
SL-ATG
x := 某符号的属性值或常量
例 x := y x, y, z := 17 —— 称为复写规则
为了实现上的方便,常希望文法符号的属性求值规则为上述简单形式的。为此,我们对现有的L-ATG的定义做一点改变,从而形成一个称为简单赋值形式的 L-ATG
目的:简单化
- 一个简单赋值形式的L-ATG除动作符号外,其余符号的属性求值规则右部是属性或是常量。
定义
一个 L-ATG 被定义为简单赋值形式的(SL-ATG),当且仅当满足如下条件:
- 产生式右部符号的继承属性是一个常量,它等于左部符号的继承属性值,或等于出现在所给符号左部某个符号的综合属性值。
- 产生式左部非终结符号的综合属性是一个常量,它等于其自身的继承属性值或等于右部某个符号的综合属性值。
L-ATG 转换为 SL-ATG
考虑产生式:
< A > → a↑R < B >↑S < C >↓I,I := f(R , S)
显然:继承属性求值规则不是简单赋值形式的,因为它需要对 f 求值。
设动作符号“@ f ” 表示函数 f 求值,该动作符号有两个继承属性和一个综合属性。
@f↓I1,I2↑S1 S1 := f(I1, I2)
修改产生式
- 插入“@ f ” 到右部的适当位置
- 引进新的复写规则(将R,S 赋给 I1 和 I2,f 值赋给 S1)
- 删去原有包含 f 的规则
< A > → a↑R < B >↑S@ f↓I1,I2↑S1< C >↓I,I1 := R,I2 := S,S1 := f( I1, I2),I := S1
该文法是简单赋值形式的L-ATG。
三、自顶向下语法制导翻译
- 翻译文法的自顶向下翻译
- 属性翻译文法的自顶向下翻译
翻译文法的自顶向下翻译——递归下降翻译器
按翻译要求,在文法中插入语法动作符号,在分析过程中调用相应的语义处理程序,完成翻译任务。
例:输入文法
- < S > → a < A > < S >
- < S > → b
- < A > → c < A > < S > b
- < A > → ε
int main() // 主程序
{
nextsym(); // 预读一个符号
if (class == 'a' || class == 'b')
{
proc_S(); // 调用S的分析程序
}
if (class != '#')
{
error();
}
return 0;
}
void proc_S() // S的分析程序
{
if (class == 'a')
{
nextsym(); // a被匹配,读下一个输入符号
proc_A(); // 调用A的分析程序
proc_S(); // 调用S的分析程序
}
else if (class = 'b')
{
nextsym(); // b被匹配,读下一个输入符号
}
else
{
error();
}
}
void proc_A() // A的分析程序
{
if (class == 'c')
{
nextsym(); // c被匹配,读下一个输入符号
proc_A(); // 调用A的分析程序
proc_S(); // 调用S的分析程序
if (class != 'b') // 匹配符号b
{
error();
}
nextsym(); // b被匹配,读下一个输入符号
}
else if (class == 'a' || class == 'b')
{
return;
}
else {
error();
}
}
翻译文法(符号串翻译文法)
- < S > → a < A > @x < S >
- < S > → b @z
- < A > → c @y < A > < S > @v b
- < A > → @ w
int main() // 主程序
{
nextsym(); // 预读一个输入符号
if (class == 'a' || class == 'b')
{
proc_S(); // 调用S的分析程序
}
if (class != '#')
{
error();
}
return 0;
}
void proc_S() // S的分析程序
{
if (class == 'a')
{
nextsym(); // a被匹配,读下一个输入符号
proc_A(); // 调用A的分析程序
printf("x"); // 输出x
proc_S(); // 调用S的分析程序
}
else if (class = 'b')
{
nextsym(); // 预读一个输入符号
printf("z"); // 输出z
}
else
{
error();
}
}
void proc_A() // A的分析程序
{
if (class == 'c')
{
nextsym(); // c被匹配,读下一个输入符号
printf("y"); // 输出y
proc_A(); // 调用A的分析程序
proc_S(); // 调用S的分析程序
printf("v"); // 输出v
if (class != 'b') // 匹配符号b
{
error();
}
nextsym(); // b被匹配,读下一个输入符号
}
else if (class == 'a' || class == 'b')
{
printf("w"); // 输出w
return;
}
else
{
error();
}
}
翻译文法的自顶向下翻译——LL(1)
根据上一章所学知识,构造如下文法的LL(1)分析器。
例:输入文法
- < A > → a < B > c < D >
- < A > → b
- < B > → c
- < B > → a < A >
- < D > → c < D >
- < D > → b

- POP; PUSH(DcB); NEXTSYM;
- POP; NEXTSYM;
- POP; NEXTSYM;
- POP; PUSH(A); NEXTSYM;
- POP; PUSH(D); NEXTSYM;
- POP; NEXTSYM;
根据字符串翻译的具体语义,在适当位置添加动作符号。对应地,分析表中的语义动作也发生了改变。
符号串翻译文法
- < A > @v a @w < B > @x c @y @z
- < A > b
- < B > c @r
- < B > a @m < A >
- < D > c < D > @n
- < D > @s b

- OUT(vw); POP; PUSH(@z D @y c @x B); NEXTSYM;
- POP; NEXTSYM;
- OUT®; POP; NEXTSYM;
- OUT(m); POP; PUSH(A); NEXTSYM;
- POP; PUSH(@n D); NEXTSYM;
- OUT(s); POP; NEXTSYM;

(字符串)翻译文法自顶向下翻译程序的设计:
构造递归下降翻译器或LL(1)翻译器的关键是要根据输入文法构造出相应的翻译文法。该翻译文法要能正确地定义输入语言到目标语言的翻译。有了这种翻译文法,根据语法制导翻译原理和第四章语法分析的知识,就不难构造出相应的翻译器。
属性翻译文法的自顶向下翻译的实现——递归下降翻译器
我们把处理翻译文法的递归下降翻译器进行适当扩展,便可得到处理属性(翻译)文法的递归下降翻译器。
方法:
对于每个非终结符号都编写一个翻译子程序(过程)。根据该非终结符号具有的属性数目,设置相应的参数。
- 继承属性:声明为赋值形参
- 综合属性:声明为变量形参
例:U↓x,↑y → …
Procedure U (x, y);
- x:赋值形参
- y:变量形参
过程调用语句的实参:
- 继承属性 : 继承属性值(传实参值)
- 综合属性 : 属性变量名(传地址,返回时有值)
关于属性名的约定:
具有相同值的属性取相同的属性名。
这样可省去不少属性求值规则
< S > → i↑a < B >↓b < C >↓c b, c := a
< S > → i↑x < B >↓x < C >↓x
产生式左部的同名非终结符使用相同的属性名。
递归下降分析法规定每个非终结符只编写一个子程序!
具有简单赋值形式的属性变量名取相同的属性名,可删去属性求值规则。
< L >↑a↓b → e↓I < R >↓J
< L >↑x↓y → < H >↓z↑w转换为:
< L >↑x↓y → e↓I < R >↓J
< L >↑x↓y → < H >↓z↑w
例:
<说明> → Type↑t id↑n @set_table↓t1,n1 <变量表>↓t2
t2, t1 := t; n1 := n;
转换为:
<说明> → Type↑t id↑n @set_table↓t,n <变量表>↓t
下面通过一个例子,详细介绍如何构造属性文法的递归下降翻译器。(该文法应是具有L-属性的符号串翻译文法)。
例:有如下属性翻译文法G[< S >]
- < S >↓R1 → a↑T1 < A >↑Q1 @x↓T2,R2 < S >↓Q2,R2 := R1; T2 := T1; Q2 := Q1
- < S >↓R1 → b @Z↓R2,R2 := R1
- < A >↑P → c↑U1 @y↓U2 < A >↑Q < S >↓Z @v↓P b,U2 := U1, P := Q + U1; Z := U1 - 3
- < A >↑P → @w,P := 8
对简单赋值形式的属性变量取相同的属性名,其求值规则可以删去。
开始符号的继承属性 R = 7。
- < S >↓R → a↑T < A >↑Q @x↓T,R < S >↓Q
- < S >↓R → b @z↓R
- < A >↑P → c↑U @y↓U < A >↑Q < S >↓Z @v↓P b,P := Q + U; Z := U - 3
- < A >↑P → @w,P := 8
声明两个全局变量 class和 token,程序nextsym() 读下一个符号,并将该符号的类型部分赋给 class,把值部分(如果有的话)赋给 token,然后将读符号的指针移到下一个符号。
int main() // 主程序
{
nextsym(); // 预读一个输入符号
if (class == 'a' || class == 'b')
{
proc_S(7); // 调用S的分析程序,继承属性R的初值为7
}
if (class != '#')
{
error();
}
return 0;
}
void proc_S(int R) // S对应分析过程,继承属性R为形式参数
{
if (class == 'a')
{
int T, Q; // 属性变量作为局部变量申明
T = token; // 将单词值赋给终结符的综合属性
nextsym(); // 读输入符号
proc_A(&Q); // 调用A的分析程序,将返回一个值,存于Q(综合属性)中
out(x↓T,R); // 调用输出函数
proc_S(Q); // 调用S的分析程序,实参为Q
}
else if (class = 'b')
{
nextsym(); // 预读一个输入符号
out(z↓R); // 调用输出函数
}
else
{
error();
}
}
void proc_A(int *P) // A的分析程序,综合属性P申明为指针变量,支持值的返回
{
if (class == 'c')
{
int U, Q, Z; // 属性变量作为局部变量申明
U = token; //单词值赋给终结符的综合属性
nextsym(); // c被匹配,读下一个输入符号
Z = U - 3; // 利用求值规则求出Z的值
out(y↓U); // 调用输出函数
proc_A(&Q); // 调用A的分析程序,将返回一个值,存于Q(综合属性)中
*P = Q + U; // 利用求值规则求出* p 的值
proc_S(Z); // 调用S的分析程序
out(v↓P); // 调用输出函数
if (class != 'b') // 匹配符号b
{
error();
}
nextsym(); // 读下一个输入符号
}
else
{
*P = 8; // 根据求值规则求值
out(w); // 调用输出函数
}
}
例:有如下属性翻译文法G[< S >]
- < S >↓R → a↑T < A >↑Q @x↓T < S >↓Q
- < S >↓R → b @z↓R
- < A >↑P → c↑U @y↓U < A >↑Q < S >↓Z @v↓P b,P := Q + U; Z := U - 3
- < A >↑P → @w,P := 8
声明两个全局变量 class和 token,程序nextsym() 读下一个符号,并将该符号的类型部分赋给 class,把值部分(如果有的话)赋给 token,然后将读符号的指针移到下一个符号。
int main() // 主程序
{
nextsym(); // 预读一个输入符号
if (class == 'a' || class == 'b')
{
proc_S(7); // 调用S的分析程序,继承属性R的初值为7
}
if (class != '#')
{
error();
}
return 0;
}
void proc_S(int R) // S对应分析过程,继承属性R为形式参数
{
if (class == 'a')
{
int T, Q; // 属性变量作为局部变量申明
T = token; // 将单词值赋给终结符的综合属性
nextsym(); // 读输入符号
proc_A(&Q); // 调用A的分析程序,将返回一个值,存于Q(综合属性)中
out("x", T); // 调用输出函数
proc_S(Q); // 调用S的分析程序,实参为Q
}
else if (class = 'b')
{
nextsym(); // 预读一个输入符号
out("z", R); // 调用输出函数
}
else
{
error();
}
}
void proc_A(int *P) // A的分析程序,综合属性P申明为指针变量,支持值的返回
{
if (class == 'c')
{
int U, Q, Z; // 属性变量作为局部变量申明
U = token; // 单词值赋给终结符的综合属性
nextsym(); // c被匹配,读下一个输入符号
Z = U - 3; // 利用求值规则求出Z的值
out("y", U); // 调用输出函数
proc_A(&Q); // 调用A的分析程序,将返回一个值,存于Q(综合属性)中
*P = Q + U; // 利用求值规则求出* p 的值
proc_S(Z); // 调用S的分析程序
out("v", *P); // 调用输出函数
if (class != 'b') // 匹配符号b
{
error();
}
nextsym(); // 读下一个输入符号
}
else
{
*P = 8; // 根据求值规则求值
out("w", *P); // 调用输出函数
}
}
void out(char c, int value)
{
printf("%c\t %d\n", c, value); // 打印输出字符及对应属性值
}
实例
例:构造将算术表达式翻译成四元组的属性翻译文法,并写出递归下降分析程序。由该属性翻译文法来描述翻译过程。
翻译的输入:算术表达式 a + b
翻译的输出: 四元组 ADD , Pa, Pb, Pr
Pa, Pb, Pr 为变量 a, b 和结果单元的地址(由编译分配)。
表达式:
( a + b ) * c
输入:
( id↑1 + id↑2 ) * id↑4
id由词法分析程序返回,↑1词法综合属性,为变量数据区地址(由编译分配)。
输出:
ADD, 1, 2, 3
MULT, 3, 4, 5
翻译文法设计:
E → E + T @ADD E → T
T →T * F @MULT T → F
F →( E ) F → id
其中:
@ADD为输出ADD四元式的动作符号
@MULT为输出MULT四元式的动作符号
- 对应于完成翻译中语义动作程序
在文法中的插入位置: 在分别处理完成两个操作数之后。
输入序列: ( a + b ) * c
翻译文法产生的活动序列: ( a + b @ADD ) * c @MULT
动作符号序列: @ADD @MULT
- 反映生成四元式的顺序,也即语法分析过程中语义程序的调用顺序
属性翻译文法的设计:
在翻译文法的基础上可设计其属性翻译文法,以便语义分析过程中生成完整的四元式,完成翻译。
- 输入符号(操作数)有一综合属性,它是该符号在数据区的地址。
- 每个非终结符有一个综合属性,该属性是由它产生的代表该子表达式在数据区中的地址(中间结果)。
- 动作符号有三个继承属性,它们分别是左右操作数和运算结果在数据区的地址
这样可得表达式的属性翻译文法——可将中缀表达式翻译成四元式
- E↑x → E↑q + T↑r @ADD↓y,z,p x, p := NEW y := q z:=r
- E↑x → T↑p x := p
- T↑x →T↑q * F↑r @MULT↓y,z,p x, p:=NEW y := q z := r
- T↑x →F↑p x := p
- F↑x → (E↑p) x := p
- F↑x → id↑p x := p
说明:
id 的综合属性 p 是数据区地址。 NEW为系统过程,返回的数据区地址。